MobX - Pass parameter to action as observable to change observable value? - javascript

I have a MobX store, and a react native class setup and working, but not as intended. I have a list of observable user preferences in the UserPreferencesStore like this:
class UserPreferencesStore {
#observable userPreferences = {
receive_upvotes_mail: 0,
receive_answers_mail: 0,
receive_comments_mail: 1
}
}
In the react native class, I want to update the above observables based on onPress of TouchableHighlight like this:
<TouchableHighlight onPress={() => this.onPressPreference('receive_upvotes_mail')}>
<Image source={require('images/Email.png')}
style={UserPreferencesStore.userPreferences.receive_upvotes_mail == 1 && styles.active} />
</TouchableHighlight>
<TouchableHighlight onPress={() => this.onPressPreference('receive_answers_mail')}>
<Image source={require('images/Email.png')}
style={UserPreferencesStore.userPreferences.receive_answers_mail == 1 && styles.active} />
</TouchableHighlight>
<TouchableHighlight onPress={() => this.onPressPreference('receive_comments_mail')}>
<Image source={require('images/Email.png')}
style={UserPreferencesStore.userPreferences.receive_comments_mail == 1 && styles.active} />
</TouchableHighlight>
and here is the onPressPreference action to update the observables that does not work...
#action onPressPreference = (preferenceName) => {
// alert(preferenceName);
UserPreferencesStore.userPreferences.preferenceName = UserPreferencesStore.userPreferences.preferenceName == 0 ? 1 : 0;
}
The preferenceName parameter gets passed perfectly, as the alert shows it, but it does not seem to work when "appended" here UserPreferencesStore.userPreferences.preferenceName to update the passed preference observable store value.
However, If I create 3 actions, one for each preference, and trigger them onPress, then it works and the observable updates the value correctly in the store, but it's a lot of code repeated, and I really want to get it to work with the passed parameter to a single action.
Any idea how to get the passed parameter to work with the single action to update the respective observable's value?

You need to use bracket notation to access a property with variable name.
UserPreferencesStore.userPreferences[preferenceName] = UserPreferencesStore.userPreferences[preferenceName] == 0 ? 1 : 0;

Related

React Component List fails to properly update the default value of Input components when Filtered

I am currently designing an application that contains a list of values in a list, called modifiers, to be edited by the user to then store for later use in calculations. To make it easier to find a specific modifier, I added a search function to the list in order to pull up the similar modifiers together to the user. However, once the user puts in a value into the filtered list and then unfilters the list, the component incorrectly assigns the values to the wrong modifiers. To be more specific, the ant design <List> component when filtered fails to put the proper defaultValue for each associated input. Namely, when I input a value into the first item in the filtered list and then unfilter it, the List incorrectly places that value within the first element on the unfiltered list, rather than the modifier it was supposed to be associated with. It should be putting the proper value with the associated element by assigning the value that its grouped with in the context I have stored, but it obviously fails to do so.
Here is the Ant Design List Component I am talking about, I have removed some callbacks that aren't necessary to understand the problem. The renderitem prop takes the dataSource prop as an input and maps all of the values into it to be inputs for the <List.Item> components.
EDIT:
I failed to mention the hook in the first line, that is utilized by the search function in order to filter the words looked through to update the list accordingly. I also removed some unnecessary inline css and components since they are not relevant to the problem to improve readability. I have also decided to give a more concrete example of my issue:
This is an image of the initial values set by the user.
This is an image immediately after searching the exact name of the modifier and the list gets filtered. Clearly, the value from the first item of the unfiltered list is being put into the input of the first item of the filtered list, which is the main problem. Now when the search is undone, everything does get properly set, so I am unsure how to fix this.
I have some ideas as to why this is occurring. I know that the input components are not being re-rendered, and rather their ids are just being swapped out when the search occurs. So if there are any ways to either forcefully re-render the input components in addition to the list sections, please tell me!
const Modifiers = () => {
const [searchFilter, setSearchFilter] = useState(military); //Only for words in search bar, "military" will be replaced with entire data set later
const context = useContext(Context)
const search = value => {
if(value != ""){
setSearchFilter(searchFilter.filter(mod => mod.name.toLowerCase().indexOf(value.toLowerCase()) != -1))
}
else {
setSearchFilter(military)
}
}
const updateContext = (e, name) => {
let id = name.toLowerCase().replace(/ /gi, "_");
if(context.modifiers[id] != undefined){
context.modifiers[id] = parseFloat(e.target.value)
}
}
return (
<Layout>
<SiteHeader/>
<Content style={{ padding: '1% 3%', backgroundColor: "white"}}>
<Typography>
<Title level={2} style={{textAlign: "center"}}>
Modifier List
</Title>
</Typography>
<List dataSource={searchFilter} header={<div style={{display: "flex"}}> <Title level={3} style={{paddingLeft: "24px"}}>Modifiers</Title> <Button shape="circle" size="large" icon={<InfoCircleOutlined/>}/> <Search allowClear placeholder="Input Modifier Name" enterButton size="large" onSearch={search}
renderItem={mod => (
<List.Item extra={parseTags(mod)}>
<List.Item.Meta description={mod.desc} avatar={<Avatar src={mod.image}/>} title={<div style={{display: "flex"}}><Title level={5}>{mod.name}: </Title> <Input defaultValue={context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] != undefined ? context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] : ""} id={mod.name} onChange={(e) => updateContext(e, mod.name)}/></div>}/>
</List.Item>
)}
/>
</Content>
</Layout>
);
}
export default Modifiers;
Here is the Context Class, the modifiers field is what is the issue currently. It only has 2 currently, but the problem persists when more are added, and these 2 modifiers are the first in the unfiltered list as well.
export class Provider extends React.Component {
state = {
name: "None Selected",
tag: String,
flag: "images/flags/ULM",
modifiers: {
army_tradition: 0,
army_tradition_decay: 0,
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
Here is what one element in the military array looks like for reference as well. The regex inside the <List.Item> component is merely converting the name field of the object into one that matches whats stored within the context.modifiers field.
export const military = [
{
name: "Army Tradition",
desc: "Adds to the rate of army tradition gained each year.",
function: "ADDITIVE",
type: "WHOLE NUMBER",
category: "MILITARY",
image: "/images/icons/landLeaderFire.png",
},
...
Thanks for any help you can give.
I have solved the issue, I replaced the "id" prop with a "key" prop (which the documentation doesn't even tell you about) and now everything works properly!

How to call onPress function in for loop. in react native

I am new to react native. I have 3 functions
function1()
function2()
function3()
and I want to run these 3 functions multiple time on single button onPress. and when user click on 4th time then again 1st function should be run and so on.
here is what I have try. But I am keep gettin red line below 4 and error like this = Identifier expected.ts(1003)
for (let i = 0; i < 4; i++) {
<Icon onPress={this.function[i]}/>
}
please help what is the best way to do that
You might need to assign a key prop to Icon like this:
<Icon key={i} onPress={this.function[i]} />
In React, any iterated component has to have a key prop, especially when mapping an array. I suppose it should be same for the for loop.
Update: If you loop Icon component, it will render multiple times. But I guess you want only one Icon component to click on. Then you might use states to iterate through functions.
<Icon onPress={() => {
this.function[i]
if (i === 3) this.setState({ i: 1 })
else this.setState({ i: i + 1 })
}} />
Don't forget to add this state statement outside render() function:
state = { i: 1 }

Autocomplete in Popover Component with search rentention

I have a problem with saving the state of the search query.
When the popover is brought into focus, the searchString starts with undefined (second undefined value in picture). When the key 'b' is pressed, the event is fired, and it sets the value to "" (initialized value). As shown, when "bart" is in the search query, console only registers "bar". Does anyone know why this behavior occurs? The end goal is that I am trying to retain the search string on selection (it disappears onclick) -> would appreciate any help with this. The main code block where these changes are happening:
<Autocomplete
open
onClose={handleClose}
multiple
classes={{
paper: classes.paper,
option: classes.option,
popperDisablePortal: classes.popperDisablePortal,
}}
value={pendingValue}
onChange={(event, newValue) => {
setPendingValue(newValue);
}}
// inputValue={searchString}
// onInputChange={(event, newValue) => {
// setSearchString(newValue);
// }}
disableCloseOnSelect
disablePortal
renderTags={() => null}
noOptionsText="No values"
renderOption={(option, { selected }) => (
<React.Fragment>
<DoneIcon
className={classes.iconSelected}
style={{ visibility: selected ? 'visible' : 'hidden' }}
/>
<div className={classes.text}>
{option.value}
</div>
</React.Fragment>
)}
options={[...suggestions].sort((a, b) => {
// Display the selected labels first.
let ai = selectedValue.indexOf(a);
ai = ai === -1 ? selectedValue.length + suggestions.indexOf(a) : ai;
let bi = selectedValue.indexOf(b);
bi = bi === -1 ? selectedValue.length + suggestions.indexOf(b) : bi;
return ai - bi;
})}
getOptionLabel={option => option.value}
renderInput={params => (
<InputBase
ref={params.InputProps.ref}
inputProps={params.inputProps}
autoFocus
className={classes.inputBase}
// onChange={(event) => {
// console.log("event.target: ", event.target);
// console.log("event.currentTarget: ", event.currentTarget);
// setSearchString(event.currentTarget);
// }}
value={searchString}
onChange={handleInputChange}
/>
)}
/>
I have tried to store the value and re-populate it using both through the Autocomplete props and the InputBase (doing it on both causes it to crash). I have added a sandbox for your ref: CodeSandbox
Appreciate all the help!
Material UI autocomplete by design resets the search value every time you select an option. If you want to by pass it, use useAutocomplete hook to fine tune the component according to your need.
As for delayed console log values, you're setting the new value and then you're console logging the old value. So obviously it will print the old value, what else did you expect?
You code should have been like this
const handleInputChange = event => {
// new value => event.currentTarget.value
// old value => searchString
// these values never mutate throughout this function call
setSearchString(event.currentTarget.value);
// searchString still remains the same here and
// won't change even if you call setState
// it remains the same throughout this entire function call
// Since Mutation is not allowed in Functional Programming
// This is perhaps why Functional Programming is
// far better than Object Oriented Programming 😉
console.log('searchString: ', event.currentTarget.value);
}
However this isn't the right way to observe state changes. Better way would be something like this,
// This will be called whenever React
// observes a change in anyState
useEffect(() => {
console.log(anyState)
}, [anyState])

Data recorded from FlatList empty on first TouchableOpacity push

Summary of Problem
Inside of my Flatlist, I want to save the value of the row the user has clicked on in an array
Each row of my FlatList is clickable by using TouchableOpaicty.
What I've tried
I've searched the internet without finding any answer and I've tried saving the data to an array in many different ways
Code
FlatList
<FlatList
data={this.state.friends}
renderItem={({item}) => {
console.log('item', item);
return(
<TouchableOpacity
onPress={() => this.add(item.key)}
>
<ImageBackground
style={styles.friendBubble}
source={require('../../assets/images/friendDisplay.png')}
>
<Text style={styles.friendText}>{item.key}</Text>
</ImageBackground>
</TouchableOpacity>
)
}}
/>
TouchableOpacity onPress function
add = (item) => {
this.setState(
{friendsInComp: [...this.state.friends, item]},
console.log('this.state.friend', this.state.friendsInComp)
)
}
Expected Results
Each time I click a row, the value of that row should be appended to the end of the array.
Actual Results
The first time a row is clicked, no value is added to the array. Each subsequent time the row is clicked, the value from the previous click is added to the end of the array.
The reason why the first time you click the button, your console log print undefined is because setState is asynchronous.. thus it directly print the state without waiting the setState to finish the process, if you want to see the latest state after setState finish, you need to put the console.log inside function..
add = (item) => {
this.setState({
friendsInComp: [...this.state.friends, item ]},
() => { console.log('this.state.friend', this.state.friendsInComp) }
)
}
and just to note when you did the [...this.state.friends, item ], it only append initial state of friends with new item that user click, thus it does not keep adding to track what user click. If you need the state to keep updated to keep track of what user click. you can do something like this.
add = (item) => {
const { friendsInComp } = this.state;
this.setState({
friendsInComp: !friendsInComp ? [...this.state.friends, item ] : [...friendsInComp, item]},
() => { console.log('this.state.friend', this.state.friendsInComp) }
)
}
use !friendsInComp if you dont define friendsInComp as empty array inside constructor. otherwise, use friendsInComp.length == 0
hope this help.

javascript React Native

Hi (Sorry for my english)
I have a problem, I consume a service that bring me a buttons array for a dynamic menu. This array I want to put in the screen, like button have a id for a action in specific.
Code
var sizes = []
for (var i = 0; i < this.state.tallas.length; i++) {
sizes.push(
<TouchableOpacity style={[styles.clothingSizeItem, this.state.idSize === this.state.tallas[i].id ? styles.activeClothingSizeItem:null]}
onPress={() => this.fetchSizeData(this.state.tallas[i].id)}>
<Text style={this.state.idSize === this.state.tallas[i].id ? styles.activeSizeText:null}>
{this.state.tallas[i].name}
</Text>
</TouchableOpacity>
);
}
View
render() {
return(
{sizes}
)
}
The problem is when I press a button from my dinamic menu, the button call a function that receive a parameter, but this parameter comes from service array. So I use "i" param from for, but this variable ("i") can't found after that the screen was rendered.
Screen Shot
error screen
Gracias!
You can use Array#map instead of a for loop:
const sizes = this.state.tallas.map(item => (
<TouchableOpacity style={[styles.clothingSizeItem, this.state.idSize === item.id ? styles.activeClothingSizeItem:null]}
onPress={() => this.fetchSizeData(item.id)}>
<Text style={this.state.idSize === item.id ? styles.activeSizeText:null}>
{item.name}
</Text>
</TouchableOpacity>
);
The reason the for loop doesn't work here is that the index variable i is incremented on every iteration of the loop, so by the time the callback ends up being called (long after the loop has completed), the value i is always it's final value (same as tallas.length).

Categories

Resources