I'm trying to figure out the best place to manage the state for lists of input in react-native but haven't found any good, thorough examples or clear guidance and I can see a few options.
For simplicity not including specifics about the tool for managing state, as my understanding is how the state is stored doesn't impact the component where it's managed.
Scenario
A screen component that receives an array of items as props to be displayed in a List of ListItems. Each ListItem includes a input, for simplicity imagine a switch (boolean).
Use cases include an array of form questions or settings to be displayed in a list and allowing user input. Pseudocode:
class SettingsView extends Component {
render () {
return (
<View>
<List style={styles.list}>
{this.props.inputArray.map((item, index) => (
<ListItem
title={item.title}
isSwitched={item.value}
key={index}
onSwitchChange = {this.onChange}
/>
))}
</List>
</View>
);
}
}
Option 1
Based on the Thinking in React page, one option that comes to mind is managing state at the Screen (SettingsView) level by creating an array of (inputArray).length in the SettingsView constructor state and have the onChange function update that array based on key.
Option 2
The second option I see is having each ListItem manage the state it's displaying. This makes sense from an encapsulation perspective to me, but then less so for managing of the state, given that the onSwitchChange function is in the SettingsView and I'm not as clear how this would work.
Are there other options I'm not considering? Admit that experience in React/RN is limited and def coming from a more object mindset like iOS's list datasource model.
Note: Another option is having the entire inputArray in state, instead of passed as props. My understanding is that state should be minimized, so was designing that only the inputs to each item in inputArray should be in the state. i.e. Form Labels (i.e. questions) are props not state.
Option 1 would be the better choice, there is this concept "Smart Components and Dumb Components"
Smart Components: typically holds the state of all the child components associated with it, it also defines the functions that is passed down to child components to modify its state.
Dumb Components: These are components that receives props which includes data and functions they typically don't have their own state and relies heavily on the parent component.
The problem is that when you create a component you need to decide whether it's smart or dumb, usually I associate a screen to a smart component, in your example it would be the SettingsView(smart) that will hold the state and function and it's children will be the dumb components but this is really application specific decision because you might have a SettingsView that are dynamic based on context and so it would be much better to make it a dumb component let's use your example above
Since Settings View relies on this.props.inputArray passed from a parent
component(I will call this ParentComponent) you couldn't modify
inputArray directly in SettingsView what you could do is pass another prop from
ParentComponent to SettingsView which is a function that modifies inputArray
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
inputArray: [],
};
this.onSwitchChange = this.onSwitchChange.bind(this); // when passing fn
}
onSwitchChange(index) { // index will come from the child component
// do some logic here based on the index then update the state
this.setState('inputArray' updatedState); // updatedState is just an example variable
}
render() {
return (
<View>
<SettingsView
onSwitchChange={(index) => () => this.onSwitchChange(index)}
inputArray={this.state.inputArray}
/>
</View>
);
}
/*
(index) => () => this.onSwitchChange(index)
this is a function that will return another function
we need this because we want to delay the execution of onSwitchChange
and capture index and associate it with that method, typically this
is used if were passing function to child component(SettingsView) which will be used as
a handler for events.
*/
}
class SettingsView extends Component {
render () {
return (
<View>
<List style={styles.list}>
{this.props.inputArray.map((item, index) => (
<ListItem
title={item.title}
isSwitched={item.value}
key={index}
onSwitchChange={this.props.onSwitchChange}
/>
))}
</List>
</View>
);
}
}
This example might be pointless because you could just use SettingsView as the parent of ListItem and other components but since SettingsView state is now managed by ParentComponent it is now a dumb component and can be used in other screens that have the specific state that SettingsView needs to operate. the general goal is to create as many dumb components as possible the reason being is that these type of components are highly reusable because you just need to pass them props to properly work.
Related
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.
Hi I am looking for the solution to change text dynamically.
I am writing code to show processing results on screen.
After some googling, I found there is a code to update text dynamically as follows.
But I would like to update text without any internal event. I want to change text from outside of the class. But I don't know how to implement it as I am a javascript and react-native beginner. There are other classes to process some functions so that I need to show the updated results using Results class which is an another component of the screen.
How can I deliver 'result' to Results class and how to update it dynamically and automatically?
class Results extends Component {
constructor() {
super()
this.state = {
log: 'Processing results'
}
}
updateText = (result) => {
this.setState({log: result})
}
render() {
return (
<View>
<Text onPress = {this.updateText}>
{this.state.log}
</Text>
</View>
)
}
}
This sounds to me that props can solve your problem.
Basically when you try to render Results class, pass along the value as a prop like below:
<Results dynamicText='HI' />
Then, from your Results class, access this external value via this.props.dynamicText as below
class Results extends Component {
render() {
return (
<View>
<Text>
{this.props.dynamicText}
</Text>
</View>
)
}
}
In addition to what #Issac answered, you can also hook up your current class to Redux and dispatch actions from another class to force state changes.
React Native and ReactJS has a different concept of how classes react to each other. Most other languages use inheritance based interactions to affect changes in classes other than itself. React itself is more composition based where changing the value/state/variable of one class requires either a state change or a prop change. The caveat to that us using Redux, which utilizes an overarching Store where any component that's connected to it can pull values or dispatch actions to change values.
I have read this answer, but I want to change the structur of code a little bit,
actually I want to set parent state from child component but i don't want to add any function in parent component
actually the expected result looks like :
class Parent extends React.Component {
constructor(props) {
super(props)
this.state={modalVisible:false}
}
render() {
return (
<Child modalVisible={this.state.modalVisible} />
<Button onClick={()=>this.setState({modalVisible:true})/>
)
}
}
class Child extends React.Component {
handler(e) {
//handle parent moadlVisible state to false again
}
render() {
return
<Modal
modalVisible = {this.props.modalVisible}>
<Button title="Close Modal" onPress={()=>this.handler(e)}/>
</Modal>
}
}
so I want to make it easy to call the child component without add some function in parent to handle the child component itself, even for closing the modal of child component
Is there a way to achieve what I want?
If you don't want any connection between the 2 components at all, then you may have to use a global state store, such as redux.
Docs can be found here:
https://redux.js.org/introduction
Redux creates a global state instead of local state between all of the components. It does require a little configuration but once you have it fully integrated, you can handle your scenario. Also as your components grow, it'll be easier to keep track of state.
Why can't I just use events?
Quote in a question here..
react.js custom events for communicating with parent nodes
The React way would be to pass callbacks down to children explicitly
via props — . There's no support for custom events w/ bubbling in
React.
The reactive programming abstraction is orthogonal:
Programming interactive systems by means of the observer pattern is
hard and error-prone yet is still the implementation standard in many
production environments. We present an approach to gradually deprecate
observers in favor of reactive programming abstractions. Several
library layers help programmers to smoothly migrate existing code from
callbacks to a more declarative programming model.
A horrid way to do it in my eyes would possibly be to use Async Storage
to store the key value, but I'm not going to suggest that.
If I want to display a bunch of heterogenous data in a virtualized list, it seems like the default way to do it is have the parent component gather up all the data so that it can create the sections to supply to the list component.
Is there any way to avoid requiring the parent component from doing this? I'd like to decouple the parent component from the data gather part, so that all it has to do is declare it has such and such components, and then those components would be responsible for gathering the data.
This would be exceedingly simple if it were a ScrollView:
<ScrollView>
<SectionA>
<SectionB>
<SectionC>
</ScrollView>
However, to leverage the performance gains of a VirtualizedList, if each section is large, I would need to pass in the individual data of each section into the VirtualizedList. I'm not sure how to do this or if it's possible.
Not sure if this is idiomatic or a gross React anti-pattern, but the way I solved it was to implement each section as purely headless data Component.
export type SectionDataComponentProps = {
onDataChanged?: () => void, // call this whenever the data updates.
}
export class SectionDataComponent<P : SectionDataComponentProps, S, ItemT> extends React.PureComponent<P, S> {
// Implemented by subclasses
getSectionData() : Array<SectionT<ItemT>> {
// returns an array of sections for a SectionList...
}
render() {
// business logic component only.
return null;
}
}
The parent component keeps track of them through the use of ref, and then calls getSectionData() as needed.
I am using Redux with React Native and I'm having problem in accessing a specific index of the redux store array with the key.
Here's a rough idea of what I'm trying to do. I have a ParentComponent that accesses the reduxStore for a state data and data contains an array of id,title in it.
class ParentComponent extends Component {
dataArray() {
return Object.keys(this.props.data).map(key => this.props.data[key])
}
/*
Consider the dataArray has elements which are an array of `id` & `title`
*/
render() {
return (
<ScrollView style={styles.fragmentContainer}>
{this.dataArray().map((element) => {
return
<ChildComponent
key={element.id}
id={element.id} />
})}
</ScrollView>
)
}
}
Now I wanna access title of each element of the array via the ChildComponent. This is what I'm trying to do to access it:
class ChildComponent extends Component {
render(
return (
<View>
<Text>{this.props.data[this.props.id].title}</Text>
</View>
)
)
}
Obviously the above doesn't work. It's just an idea of what I'm trying to do. I am unable to access the redux store data using a given key. I tried to find out how one can access it but I found no solution. Is my method of accessing the state for the ChildComponent wrong. Can anyone refer me to how I can achieve this?
NOTE: I specifically want to access the state via the ChildComponent. If that's not how it's supposed to work like, please refer me to some other way or some documentation that explains how this is supposed to be done.
If your child component is a class component, you can import connect, set up your reducer in mapStateToProps and bind them in your export.
You can also send the reducer prop value as props from parent to child component. Something like this <Child value={this.props.myData} />. Now in your child component you can refer it as this.props.value. If you use this approach, it doesn't matter if your child is a class component or not. Don't forget to also instance value in your child component creation.
Hope it helps.
First of all I don't see you passing a data prop from your parent to child. Thus you won't get this.props.data in your child component.
I don't understand why would you want to extract title that way when you can pass it as a prop just like you passed id.
If you specifically want to access redux store, then you use connect from react-redux.
Here's a link to see how to do that
http://redux.js.org/docs/basics/UsageWithReact.html