React-Native how to update dropdown - react-native

I'm using react-native-chooser to create my dropdowns and when I select 1 item from dropddown1 I want to update the items from dropdown2. Thanks.

In react, to make your UI change, you need to update your state. From looking at the docs for react-native-chooser it has a callback method called onSelect. Here, the currently selected option is returned to you to use. Based on this selected option you can update the state of second dropdown. The important part here is the parent child relationship. In react, a child is only re-rendered if the parent's state is updated (unless otherwise specified). Some pseudocode:
// Your method callback
onSelect = (option) => {
const newOptions = computeNewOptions(option)
this.setState({options: newOptions})
}
// Your Second dropdown component would take these options in as a prop
render () {
return (
<SecondDropDown options={this.state.options} />
)
}
// You can then access your options through the props
export default class SecondDropDown extends React.Component {
render () {
let myOptions = renderOptions(this.props.options)
return (
<View>
{myOptions}
</View>
)
}
}

Related

Lifting state up does not work in React native

I probably misunderstood something very basic here.
I want to run a method in my child component whenever the state "oranges" in my parent component changes.
My Child component looks like this:
const [someText, setSomeText] = useState(props.oranges);
const consoleThis= () => {
if (someText == true){
console.log("win!")
}else{
return
}
};
useEffect(() => {
consoleThis();
}, [someText]);
This is the code in my parent component
...
const [apples, setApples] = useState(false);
<child
oranges ={apples}
/>
...
<Pressable
onPress={() => {
setApples(true);
}
>
Any ideas why I don't get my "win!" console logged?
useState(initial) doesn't reset the state each time the value passed to it changes - it is only used for setting the initial state of the value. Passing a new prop value will not update the state variable value - the state value becomes its own copy of the initial value that is managed independently of the prop value. In general, when you're lifting the state up, you don't want to also be keeping state in your child component. Instead, just drive the child component off of the state value passed via props.
You probably want something that responds to changes in the orange prop, like this:
function ChildComponent({orange}) {
useEffect(() => {
if (orange) {
console.log("win!")
}
}, [orange])
};

Retrieve state values from many of the same child component react native

I have a screen that contains many of the same CustomSlider component. I would like to retrieve the slider values from every slider.
What is best practice for doing this in react native?
Here's a minimum working example, with 3 sliders:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import MultiSlider from "#ptomasroos/react-native-multi-slider";
class CustomSlider extends Component {
constructor(props) {
super(props)
this.state = {
multiSliderValue: [1, 9]
}
}
multiSliderValuesChange = values => {
this.setState({multiSliderValue: values});
};
render(){
return (
<MultiSlider
values={this.state.multiSliderValue}
onValuesChange={this.multiSliderValuesChange}
min={0}
max={10}
step={1}
/>
)
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
};
}
get_slider_values = () => {
// what is best practice to access the values of every slider here?
// eg an object like this
const slider_values = [[1.4, 7.4], [4.3, 7.0], [1.9, 3.2]]
return slider_values
}
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', padding: 50}}>
<CustomSlider />
<CustomSlider />
<CustomSlider />
<Text>{`The slider values are: ` + JSON.stringify(this.get_slider_values())}</Text>
</View>
);
}
}
There is no need for a complex solution. The way that I would handle this is to manage the state in the parent component. The CustomSlider doesn't really need to know its state. As the parent component needs to know the state of the sliders it is better to handle it there.
So as the parent component is going to handle the state this means we need to make some changes to what you are doing.
Set initial values in the parent component for the state of each of the sliders. This is important, it makes it means that even if the user doesn't touch the sliders we know the values of them.
Pass a function to each of the sliders that calls back to the parent component.
As the parent component is controlling the state we can remove the state from the CustomSlider. This gives a few options we could leave it as a Component, change it to a PureComponent or go one step further an change it to a Functional Component If the slider doesn't really need to know its state then the last option should be best for performance.
Here is how I would refactor your App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
sliderValues: [[1, 9],[1, 9],[1, 9]] // we should control the state here
};
}
// this uses function currying to bind the function and pass a value to it
onChange = (index) => (values) => {
this.setState( prevState => {
let sliderValues = prevState.sliderValues;
sliderValues[index] = values;
return {
sliderValues
}
})
}
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', padding: 50}}>
<CustomSlider intialValues={this.state.sliderValues[0]} onChange={this.onChange(0)}/>
<CustomSlider intialValues={this.state.sliderValues[1]} onChange={this.onChange(1)}/>
<CustomSlider intialValues={this.state.sliderValues[2]} onChange={this.onChange(2)}/>
<Text>{`The slider values are: ` + JSON.stringify(this.state.sliderValues)}</Text>
</View>
);
}
}
Notice how we don't actually need a function to get the values of the sliders as they are stored in state. That means we can access the sliders' values directly by using this.state.sliderValues.
Here is your CustomComponent refactored to work with the above code:
class CustomSlider extends Component { // this could easily be swapped for a PureComponent
render(){
return (
<MultiSlider
values={this.props.intialValues}
onValuesChange={this.props.onChange}
min={0}
max={10}
step={1}
/>
)
}
Notice how it doesn't need to manage state at all as the parent component is handling it. It also means that we can remove a lot of code that isn't actually necessary. This is why I think we can go one step further and make it a Functional Component
const CustomSlider = ({intialValues, onChange}) => {
return (
<MultiSlider
values={intialValues}
onValuesChange={onChange}
min={0}
max={10}
step={1}
/>
)
}
If however if the CustomSlider needs to know its state because it is doing something more than capturing the values of the slider then you can easily add state to it by using it as a Component or a PureComponent.
Snack
Here is a snack showing the above code working. I have shown all three possible components and have used them in the App.js. There isn't much difference in how they look, but your use case will determine which one that you use. https://snack.expo.io/#andypandy/multisliders
Best Practice
The best practice is to go for the simplest solution that you can find. Ideally that would be a Functional Component, then a PureComponent, and finally a Component. It is also important to think about where and how the state is going to be used. Some questions that I ask myself are:
Does a component really need to know its own state?
Where do I plan on using that state?
How long do I need these state values for?
Do I need to persist that state?
What tools are available to me based on what I am currently using?
Do I really need to add another dependency or more to make this work?
If you need the values from the sliders in multiple places in your app you can use some of the features that are provided by react-native or your navigation to pass these values around. Redux and MobX are big overheads in terms of complexity and should only really be used if you need a global state management system, for the majority of cases they can be avoided.
You can store the state dynamically by some key given to each child, and access each ones state by the key you give it.
One way is to pass a closure from parent component to CustomSliders as props and monitor the changes.
<CustomSlider idx={n}
theClosurePassedThrough= (n, values) => {
// update the parents states here accordingly
}
>
Then call this closure at appropriate time.
multiSliderValuesChange = values => {
this.setState({multiSliderValue: values});
this.props.theClosurePassedThrough(this.props.idx, values);
};
The best practice, though, is to use MobX or Redux.

How to update attribute value by using ref in react native?

In below code there is "opened" attribute and I want to change its value by using ref. Here I am using ref as indexed array.
<Menu renderer={renderers.SlideInMenu} ref={(Menu) => { this.rowRefs[item.id] = Menu; }} opened={false}>
I tried it as
function updateRef(id){
React.findDOMNode(this.refs.id).setAttribute("opened", true);
}
Can anyone please explain how to create an indexed reference and how to use it?
Props should be immutable and for the purpose of dynamically change update them you should consider to set them via state.
Your code should look like:
<Menu renderer={renderers.SlideInMenu} ref={component => this.menuRef = component }} opened={this.state.opened}>
In which case the <Menu .. > is assumed to be rendered in a component which has a state variable opened which you can change using this.setState({opened: true}) . This state change will make your UI rerender hence <Menu .. > will be rendered with opened={true}.
Also if you want to use ref, then you should consider making a state variable inside Menu which should be initialized with opened prop, and you should have a method in the Menu which will change the state.
Your code should look like below:
class Menu extends React.Component {
constructor (props) {
super(props);
this.state = {
menuOpened: props.opened
}
}
changeMenuOpened = (value) => {
this.setState({
menuOpened: value
})
}
.....
}
and then you can just call the changeMenuOpened method using Menu's ref from the parent.
this.menuRef.changeMenuOpened(true);

React Native: Update ListView Row when props changes

I do have a ListView component with a renderRow() function. My custom ListBountiesView component which renders a ListView Row also takes a prop called cr and displays some content in each row depending on the value of cr.
The problem is that when this.props.customerRelationship changes its value, the ListView rows do not get updated.
I am doing it with: this.props.customerRelationship.points += responseJson.points;
I guess that ListView only updates when the data attribute changes, but how can I move props to my renderRow component so that they also update the ListView?
SuperScreen.js
renderRow(bounty) {
const { customerRelationship } = this.props;
return (
<ListBountiesView
key={bounty.id}
bounty={bounty}
cr={customerRelationship}
onPress={this.openDetailsScreen}
/>
);
}
render() {
const { bounties } = this.props;
return (
<ListView
data={bounties}
renderRow={this.renderRow}
loading={loading}
/>
)
The ListView refreshes the data source when the data prop gets a new reference:
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setState({ dataSource: this.listDataSource.clone(nextProps.data) });
}
if (nextProps.loading !== this.props.loading) {
this.setLoading(nextProps.loading);
}
}
Similarly, React Native's ListView refreshes when its data source changes. This was discussed on their GitHub and in many other threads. The generally accepted workaround is to create a new data array. In your case, do it in componentWillReceiveProps whenever customerRelationship changes.
Move your customerRelationship to bounty object. Each bounty should have this property, then check it's value changed in the rowHasChanged. Other way is to check customerRelationship in componentWillReceiveProps function, if it's value changed clone bounties so all of it's child have new object reference.

React Native + Redux: How to subscribe to changes in state?

I have a component and I want to call a method checking the state whenever it changes. This is my component with a dummy method to demonstrate what I want to do (animate the view offscreen if onboarding.show === false):
export class Onboarding extends Component {
animateView() {
// i want to call this method when
// the state changes
// something like;
if (!this.props.onboarding.show) {
Animated.spring(...);
}
}
render() {
const { onboarding, finish } = this.props;
return (
<Animated.View>
...
</Animated.View>
);
}
}
...
export default connect(
state => {
return {
onboarding: state.onboarding,
};
},
dispatch => {
return {
};
}
)(Onboarding);
Is there a way to subscribe to the changes in state?
== UPDATE ==
as requested, here's what my slideOffScreen method does:
slideOffScreen() {
Animated.timing(this.state.offsetX, {
toValue: -Dimensions.get('window').width,
duration: 350,
easing: Easing.elastic(),
}).start();
}
The react-redux connect method wraps the component with a container component that is aware of the store's state changes. Whenever the state changes, connect re-renders the wrapped component (Onboarding in your case).
According to the redux docs:
Technically, a container component is just a React component that uses
store.subscribe() to read a part of the Redux state tree and supply
props to a presentational component it renders. You could write a
container component by hand, but we suggest instead generating
container components with the React Redux library's connect()
function, which provides many useful optimizations to prevent
unnecessary re-renders.
If your component doesn't re-rendered when the state changes, check if you're not mutating the state instead of replacing it. Redux checks if the state changed by shallowly comparing the old state, and the new state (comparing only the references, and not the values).
For example, to add an item to an array, you can't use array.push(item) because that won't create a new array, just mutate the existing one. Instead you'll have to use something like array.concat(item), which does.
To update objects, you can see in the redux docs under handling actios example, you can see that to create a new state:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.
Looks like this works:
componentWillReceiveProps(props) {
if (!props.onboarding.show) {
this.slideOffScreen();
}
}
not sure if there's a way to do it through the redux API