Hi I'm wondering why this code works,
Sorry for the sintax errors, this is an Example. My question is why memberIpAssignments is taking ip value?. I don't get it if I'm no passing ip into setMemberIpAssignments(arr =>[...arr]) but still takes that's value and updating the state.
setMemberIpAssignments(arr =>[...arr]), this state shouldn't change at all, because I'm no giving ip value. But it does change taking ip value.
if someone can explain to me I'll be grateful.
I'm new at react-native
export const zeroTierNetworkMembersUpdateScreen = ({ route }) => {
const { ipAssignments } = ["192.168.0.1","192.168.1.1"];
const [memberIpAssignments, setMemberIpAssignments] =(ipAssignments);
return (
<View style={styles.viewSet}>
{memberIpAssignments.map((eachIpAssignments, index) => {
return (
<Input
key={index}
placeholder={"ipAssignments"}
keyboardType={"default"}
value={eachIpAssignments}
onChangeText={(value) => {
var ip = ipAssignments;
ip[index] = value;
setMemberIpAssignments(arr =>[...arr]);
}}
/>
);
})}
</View>
);
};
I think I've confirmed my suspicions that you are in fact mutating an object reference that you've stored in local component state.
export const zeroTierNetworkMembersUpdateScreen = ({ route }) => {
// (1) ipAssignments array reference
const ipAssignments = ["192.168.0.1", "192.168.1.1"];
// (2) memberIpAssignments references ipAssignments
const [memberIpAssignments, setMemberIpAssignments] = useState(ipAssignments);
return (
<View style={styles.viewSet}>
{memberIpAssignments.map((eachIpAssignments, index) => {
return (
<Input
key={index}
placeholder={"ipAssignments"}
keyboardType={"default"}
value={eachIpAssignments} // (3) value from memberIpAssignments
onChangeText={(value) => {
// (4) ip references ipAssignments & memberIpAssignments
var ip = ipAssignments;
// (5) element mutation!!
ip[index] = value;
// (6) state update to trigger rerender
setMemberIpAssignments(arr => [...arr]);
}}
/>
);
})}
</View>
);
};
As far as I can tell the mutation happens exactly once since initially everything is reference the original ipAssignments array. Upon updating state though, arr => [...arr] is returning a new array reference for memberIpAssignments the references back to ipAssignments is broken.
You should really be using a functional state update to "edit" the ip entry any way. Consider the following:
export default function App() {
const ipAssignments = ['192.168.0.1', '192.168.1.1'];
const [memberIpAssignments, setMemberIpAssignments] = React.useState(
ipAssignments
);
return (
<View>
{memberIpAssignments.map((eachIpAssignments, index) => {
return (
<TextInput
key={index}
placeholder={'ipAssignments'}
keyboardType={'default'}
value={eachIpAssignments}
onChangeText={(value) => {
setMemberIpAssignments((arr) =>
arr.map((el, i) => (i === index ? value : el))
);
}}
/>
);
})}
</View>
);
}
Expo Snack
Related
I've got a useState variable that has an array of JSON objects, and I'm trying to get the text fields to dynamically render and update with onChangeText, but I'm having a bit of an issue with this.
When I add console.log(index) to updateHashtags, it says it is undefined, and I can't understand what I'm doing wrong or can do to make this work. In theory, the index should be a static number for each text field. So the first text field would have hashtags[0] as its value and use '0' as the index for updating the state of hashtags.
When I use:
console.log('index',index);
inside of updateHashtags, I get:
index undefined
Here's the code:
const updateHashtags = (text, index) => {
let ht = hashtags;
ht[index].name = text;
setHashtags(ht);
}
const hashtagElement = (
<>
<Text style={styles.plainText} >Set Your Values:</Text>
<Text style={styles.instructionsText} >This is what other users use to search for you.</Text>
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
onChangeText={(text,index) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
<TouchableOpacity
onPress={() => {
let ht = hashtags;
let newht = {
name: '',
weight: 0,
};
ht[ht.length] = newht;
setHashtags(ht);
}}
>
<Ionicons
name="add-circle-outline"
size={40}
color={'black'}
/>
</TouchableOpacity>
</>
);
Maybe the onChangeText functions doens't get index param.
Try it like this:
// we are getting index from here
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
// so no need of taking index from param here
onChangeText={(text) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
Also for your updateHashTags functions consider this insted:
const updateHashtags = (text, index) => {
// doing it this way ensures your are editing updated version of state
setHashtags((state) => {
let ht = state;
ht[index].name = text;
return ht;
});
}
I am new to react native and have a situation where I am trying to set a state to a filtered version of another state(array). With the current code, each item in the unfiltered array is being changed to the filtering condition. How can I setFilteredJobs to only contain 'jobs', where status equals the status that the user has chosen in the AppPicker?
Here is my code:
const [jobs, setJobs] = useState()
const [filteredJobs, setFilteredJobs] = useState()
const [status, setStatus] = useState()
const handleStatusChange = (item) => {
setFilteredJobs(
jobs.filter( job => job.status = item.label )
)
setStatus(item)
}
return (
<View style={defaultStyles.screenNoPadding}>
<AppTextInput placeholder='Search for a Job' icon='magnify' />
<View style={styles.filterContainer}>
<AppPicker
color='white'
selectedItem={category}
onSelectItem={item => handleCategoryChange(item)}
items={categories}
placeholder='Filter'
icon='apps' />
<AppPicker
color='white'
selectedItem={status}
onSelectItem={item => handleStatusChange(item)}
items={statuses}
placeholder='Status'
icon='apps' />
</View>
<FlatList
style={styles.list}
data={filteredJobs ? filteredJobs : jobs}
keyExtractor={job => job.id.toString()}
renderItem={({ item }) => (
<ListItem
company={item.company}
position={item.position}
status={item.status}
onPress={() => navigation.navigate('Details', { item })}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
/>
</View>
);
Thanks in advance! Keep in mind jobs is fetched in a useEffect on loading the component.
It is because you should use a comparison, and not an attribuition.
const handleStatusChange = (item) => {
setFilteredJobs(
// FIX HERE, USE == INSTEAD OF =
jobs.filter( job => job.status == item.label )
)
setStatus(item)
}
#guilherme is right, it's a simple mistake, you assigned instead of comparing. Get in the habit of using === to compare strings.
Also the way to tackle these problems in the future: console.log before and after the thing you are doing that isn't working. It would have jumped out at you pretty quick if you had. Use JSON.stringify for logging of objects if you are getting [Object object] in the logging output.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I have the following functional component and de-structuring the parameter props:
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup.fieldArrayName.map((myTime, index) => (
and I am calling the component with the following props:
<MyTimes
myWindowGroup={myWindowGroup}
fieldArrayName={"myTimes"}
name={`myWindowGroups.${index}.myTimes`}
/>
My question is as I am new to React and that is, how can I pass/use the fieldArrayName={"myTimes"} string value of myTimes into the MyTime component above so that I can replace the value of fieldArrayName.map to be myTimes.map ?
I've tried it the way it is and not working.
Use dynamic keys. See Bracket Notation.
myWindowGroup[fieldArrayName].map(....
const myWindowGroup = {
myTimes: ['This is the value array you want'],
foo: [],
};
console.log(myWindowGroup['myTimes']);
console.log(myWindowGroup['foo']);
You can just do
{myWindowGroup[fieldArrayName].map((myTime, index) => (
As I understand your question correctly, you can achieve desired output by following
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup[fieldArrayName].map((myTime, index) => (
// your logic here
))}
So I'm building a DnD app for character sheet management, where it displays the skills and whatnot.
In it I have a class(AbiltityClass) that stores a map of other classes(SkillClass) inside of a variable(_aSkills).
export default class AbilityClass {
constructor(name, aVal, aMod) {
this._abilityName = name; // string
this._abilityVal = aVal; // int
this._abilityMod = aMod; // int
this._aSkills = new Map(); // map of SkillClass objects
}
}
export default class SkillClass {
constructor(name, prof, mod, bonus){
this._nameSkill = name; // string
this._profSkill = prof; // bool
this._modBonus = bonus; // int
this._modSkill = this.evalMod(mod); // int
}
evalMod(mod) {
return mod + this._modBonus;
}
}
The _aSkills variable was originally an Array of SkillClasses, but due to ease-of-accessing, I decided that a Map would be better.
I have a process that will basically iterate through each element in _aSkills and create components out of them through the skillFactory and buildSkill functions which should be placed in the brackets where the skillFactory function is called.
const AbilityContainer = (props) => {
const buildSkill = (value, key) => {
return(
<SkillSet
key={Math.random()}
skillName={key}
prof={value._profSkill}
skillVal={value._modSkill}
setProf={()=>{}}
/>
);
}
const skillFactory = () => {
return(props.ability._aSkills.forEach(buildSkill));
}
return(
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.header_text}>
{props.ability._abilityName}
</Text>
</View>
<View style={styles.statBox}>
<View style={styles.ability}>
<StackedHex isAbility={true} lowerVal={props.ability._abilityVal}/>
</View>
<View style={styles.skills}>
{skillFactory()} // =================> The function call in question
</View>
</View>
</View>
);
};
My issue is that either skillFactory or buildSkill is not returning the SkillSet component.
I've used some print logs and I've verified that buildSkill is receiving the correct data, but something isn't working. I don't get any errors or warnings, and the place where the SkillSet components should be is just empty.
As I said, I changed the _aSkills variable from an Array to a Map recently, and it was working as an Array. This is the format of my previous code:
const AbilityContainer = (props) => {
const buildSkill = (skill) => {
return(
<SkillSet
key = {Math.random()}
prof={skill._profSkill}
setProf={skill._profFunc}
skillName={skill._nameSkill}
skillVal={skill._modSkill}
/>
);
}
const skillFactory = () => {
return (props.ability._aSkills.map(x => buildSkill(x)));
}
return(
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.header_text}>
{props.ability._abilityName}
</Text>
</View>
<View style={styles.statBox}>
<View style={styles.ability}>
<StackedHex isAbility={true} lowerVal={props.ability._abilityVal}/>
</View>
<View style={styles.skills}>
<SkillSet
prof={props.ability._saveProf}
setProf={props.ability._saveProfFunc}
skillName={'Save'}
skillVal={props.ability._saveMod}
/>
{skillFactory()}
</View>
</View>
</View>
);
};
I'm using Android Studio for this build and I'm still relatively new to react-native. I triple-checked all my other components and their stylesheets to make sure they weren't interfering, so I know it has something to do with the AbilityContainer.
I've read up on the forEach function for Maps and as far as I can tell, I'm doing it right, but I feel like there is something that forEach is doing that I don't understand in terms of how it returns things.
Does anyone have any idea what the issue could be?
Thanks in advance.
forEach does something for each element, but doesn't return anything (even if you return in the callback, that's just returning from the cb, not the equivalent of returning from a .map) which is why that version isn't working. If you convert it back to an array ([...props.ability._aSkills].map...) this will give you an array of arrays ([key, value]) from your Map. You could also use a for..of loop over _aSkills.entries().
So now knowing that forEach doesn't return anything, I just added a variable and gave my buildSkill function something to return to.
Here's what I did:
const AbilityContainer = (props) => {
let skills = new Array();
const buildSkill = (value, key) => {
skills.push(<SkillSet
key={Math.random()}
skillName={key}
prof={value._profSkill}
skillVal={value._modSkill}
setProf={()=>{}}
/>);
}
const skillFactory = () => {
props.ability._aSkills.forEach(buildSkill);
return(skills);
}
...
};
This is a bit of an open ended question, as I'm sure the way I'm going about this is incorrect. But I'm curious why React isn't re-rendering as I would expect. I suspect it has to do with the behavior of the useState hook paired with a functional component.
The code is in this CodeSandbox link, and code noted below:
function App() {
var foosList = ["foo1", "foo2", "foo3"];
const [selectedFoo, setSelectedFoo] = useState(-1);
const isSelected = i => i === selectedFoo;
return (
<div className="App">
<FoosWrapper>
<TitleSpan>Foos</TitleSpan>
<ListGroup>
{foosList.map((fooItem, i) => (
<ListGroupItem
key={fooItem}
active={isSelected(i)}
onClick={() => setSelectedFoo(i)}
>
{fooItem}
</ListGroupItem>
))}
</ListGroup>
</FoosWrapper>
<BarsWrapper>
<TitleSpan>Bars</TitleSpan>
<Bars foo={foosList[selectedFoo]} />
</BarsWrapper>
</div>
);
}
const Bars = props => {
const [pendingBar, setPendingBar] = useState("");
const [newBars, setNewBars] = useState([]);
const keyPress = e => {
if (e.key === "Enter") {
save(pendingBar);
}
};
const save = bar => {
newBars.push(bar);
setNewBars([...newBars]);
};
return (
<div>
<ListGroup>
<ListGroupItem key={props.foo}>{props.foo}</ListGroupItem>
</ListGroup>
<ListGroup>
{newBars.map(newBar => (
<ListGroupItem key={newBar}>{newBar}</ListGroupItem>
))}
</ListGroup>
<InputGroup>
<Input
placeholder="Add a bar"
onChange={e => setPendingBar(e.target.value)}
onKeyPress={keyPress}
/>
</InputGroup>
</div>
);
};
Broadly, there are two logical widgets: Foos and Bars. Foos on the left, Bars, on the right. I'd like to have a user select a 'foo', and a distinct list of bars associated with said 'foo' is displayed on the right. A user may add new bars to each respective 'foo'. Can think of foo having a parent relationship to bar.
The Bars component maintains a list of bars added by the user. My expectation is the Bars component would re-render the internal newBars collection when a new foo is selected. However, that state hangs around and is displayed regardless of what 'foo' is selected on the lefthand side.
That seems weird, but perhaps I'm not thinking of React functional components and hooks in the right way. Would love to understand why this behavior exists, and additional would love to hear proposed approaches that make more sense.
Any insight is greatly appreciated!
If you want the hierarchy to be reflected, then initialize the state of your bars so that it synchronizes with the state of your foos. Right now, your Bars is a single component maintaining its own state independently of App. Here's how I would approach this particular relationship.
function App() {
const foos = useMemo(() => ["foo1", "foo2", "foo3"], []);
const [bars, setBars] = useState(foos.map(() => []));
const [selectedIndex, setSelectedIndex] = useState(0);
const setBar = useCallback(
bar => {
setBars(bars => Object.assign(
[...bars],
{ [selectedIndex]: bar }
));
},
[setBars, selectedIndex]
);
const isSelected = useCallback(
index => index === selectedIndex,
[selectedIndex]
);
const foosList = useMemo(
() => foos.map((foo, index) => (
<ListGroupItem
key={foo}
active={isSelected(index)}
onClick={() => setSelectedIndex(index)}
>
{foo}
</ListGroupItem>
)),
[foos, isSelected, setSelectedIndex]
);
return (
<div className="App">
<FoosWrapper>
<TitleSpan>Foos</TitleSpan>
<ListGroup>{foosList}</ListGroup>
</FoosWrapper>
<BarsWrapper>
<TitleSpan>Bars</TitleSpan>
<Bars
foo={foos[selectedIndex]}
bars={bars[selectedIndex]}
setBars={setBar}
/>
</BarsWrapper>
</div>
);
}
function Bars({ foo, bars, setBars }) {
const [pendingBar, setPendingBar] = useState("");
const barsList = useMemo(
() => bars.map(bar => (
<ListGroupItem key={bar}>{bar}</ListGroupItem>
)),
[bars]
);
const save = useCallback(
bar => { setBars([...bars, bar]); },
[setBars, bars]
);
const change = useCallback(
event => { setPendingBar(event.target.value); },
[setPendingBar]
);
const keyPress = useCallback(
event => { if (event.key === "Enter") save(pendingBar); },
[pendingBar, save]
);
return (
<div>
<ListGroup>
<ListGroupItem key={foo}>{foo}</ListGroupItem>
</ListGroup>
<ListGroup>{barsList}</ListGroup>
<InputGroup>
<Input
placeholder="Add a bar"
onChange={change}
onKeyPress={keyPress}
/>
</InputGroup>
</div>
);
}
I may have gone a bit overboard with the memoization hooks, but that should give you at least an idea of what and how various values can be memoized.
Keep in mind that the second argument is the array of dependencies which determines whether the memoized value is recomputed or retrieved from cache. The dependencies of hooks are checked by reference, much like a PureComponent.
I also opted to initialize selectedIndex to 0 to avoid addressing the issue of how to handle the render function for Bars when no foo is selected. I'll leave that as an exercise to you.
If you want to show the bars for the selected foo, you need to structure your "bars" data accordingly. The simplest way to do this is to keep the "bars" data in the format of an object instead of an array. You can write the code as below.
const Bars = props => {
const [pendingBar, setPendingBar] = useState("");
const [newBars, setNewBars] = useState({});
const { foo } = props;
const keyPress = e => {
if (e.key === "Enter") {
save(pendingBar);
}
};
const save = bar => {
if (!foo) {
console.log("Foo is not selected");
return;
}
const bars = newBars[foo] || [];
bars.push(bar);
setNewBars({
...newBars,
[foo]: [...bars]
});
};
return (
<div>
<ListGroup>
<ListGroupItem key={props.foo}>{props.foo}</ListGroupItem>
</ListGroup>
<ListGroup>
{newBars[foo] &&
newBars[foo].map(newBar => (
<ListGroupItem key={newBar}>{newBar}</ListGroupItem>
))}
</ListGroup>
<InputGroup>
<Input
placeholder="Add a bar"
onChange={e => setPendingBar(e.target.value)}
onKeyPress={keyPress}
/>
</InputGroup>
</div>
);
};