How to make every React Component reload based on selected value of picker? - react-native

How can I force reload certain or all react Components when a particular value is selected using Picker?
import React from 'react';
import { Picker, Text, View } from 'react-native';
export default class Test extends React.Component {
render(){
return (
<View>
<TestComponent />
<Text>
{defaultKey + ' from defaultKey from main class'}
</Text>
</View>
);
}
}
let myArr = [
{
"key": "key0",
"value": "value0"
},
{
"key": "key1",
"value": "value1"
}
];
let defaultKey = "key0";
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {
selectedValue : "value0",
PickerValueHolder : "value0"
}
}
render(){
return (
<View>
<Picker
selectedValue ={ this.state.PickerValueHolder }
onValueChange ={ (itemValue, itemIndex) => {
this.setState({ PickerValueHolder: itemValue });
defaultKey = myArr[itemIndex].key;
defaultIndex = itemIndex;
}
}
>
{ myArr.map((item, key)=>(
<Picker.Item label={ item.key } value={ item.value } key={ key } />)
)}
</Picker>
<Text>
{defaultKey + ' from defaultKey from TestComponent'}
</Text>
</View>
);
}
}
In this case, the <Text> in the default class is not reloading. How do I make it reload? And how can I force reload Imported components as well?

React Components renders / refresh whenever state / props changes. If you want to re render current component change it's state. If you want to render the child component change it's props. If you want to re render the parent component use a callback and modify the state in callback.

You could use global state management like redux, but if you're unfamiliar with that, you can send a handler into the Test Component as a callback to the Parent via the props.
Changing current component value use
this.state
Changing a child component value use and pass the prop in by
Inserting Props to child:
<ChildComponent prop1={value}/>
Getting the value from the child component:
this.props.prop1
Updating the Parent state is similar to the Above but requires the prop to be a function passed from the parent.
handler () {// some function in parent component}
<ChildComponent prop1={this.handler}/>
It would mean it would look something like below:
import React from 'react';
import { Picker, Text, View } from 'react-native';
export default class Test extends React.Component {
constructor(props) {
super(props)
this.handler = this.handler.bind(this);
this.state = {
defaultKey : "somevalue"
};
}
handler(value) {
this.setState({
defaultKey: value
})
}
render(){
return (
<View>
<TestComponent handler={this.handler}/>
<Text>
{this.state.defaultKey + ' from defaultKey from main class'}
</Text>
</View>
);
}
}
let myArr = [
{
"key": "key0",
"value": "value0"
},
{
"key": "key1",
"value": "value1"
}
];
let defaultKey = "key0";
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {
selectedValue : "value0",
PickerValueHolder : "value0"
}
}
render(){
return (
<View>
<Picker
selectedValue ={ this.state.PickerValueHolder }
onValueChange ={ (itemValue, itemIndex) => {
this.setState({ PickerValueHolder: itemValue });
defaultKey = myArr[itemIndex].key;
defaultIndex = itemIndex;
this.props.handler(defaultKey);
}
}
>
{ myArr.map((item, key)=>(
<Picker.Item label={ item.key } value={ item.value } key={ key } />)
)}
</Picker>
<Text>
{defaultKey + ' from defaultKey from TestComponent'}
</Text>
</View>
);
}
}
UPDATE:
The OP has asked about why to use redux (as some of the components are not nested through the Test class.
What is redux?
Redux is a predictable state container for JavaScript apps.It helps you write applications that behave consistently.
Check out the docs here: https://redux.js.org/
Essentially, you've entered an issue a lot of people face when building an application that needs to share state between components in React/React Native. In every component you have a local state (this.state) - Redux includes a global App State, therefore in your classes where you change your default value, you can update the global store (which all components have access to). The value which you display the defaultValue would be from the global store.
Essentially, components which are not related to each other, will not know about each others state you'll need to use a global store such as redux. I suggest you do some research on the best technique for you. One way you could achieve it at present would be to wrap the whole application in a parent component and pass the props down from there, such as
-- ParentComponent (set state here)
-- ChildComponent1 (Pass Props)
-- ChildComponentOf1 (Pass Props)
-- ChildComponent2 (Pass Props)
For some further reading check out some posts here:
How to update parent's state in React?
How to share state among multiple scenes of a Navigator in React-Native

Related

passing dynamic data through props

I'm trying to pass data as props to an encapsulated component.
I need to change that date as it is changing in the parent.
But nor directly passed (like textCaption), nor passed as a function-generated - does not allow to update data in the child upon state change in the parent.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this._statData = props.textData;
this._dynData = props.funcData;
}
render() {
console.log(`Render MyComponent with static=${this._statData} dynamic=${this._dynData()}`)
return (
<Text>static: { this._statData} / dyn: { this._dynData()}</Text>
)
}
}
const Container = () => {
const [textCaption, setTextCaption] = React.useState("Textual")
const [funcCaption, setFuncCaption] = React.useState("Functional");
console.log(`textCaption: ${textCaption}, funcCaption: ${funcCaption}`)
return (
<View style={styles.container}>
<Button title="Change" onPress={()=>{
console.log(`Update dynamic prop`)
setTextCaption(textCaption + "!")
setFuncCaption(funcCaption + "!")
}}/>
<MyComponent textData={textCaption} funcData={()=>funcCaption}/>
</View>
)
}
export default class App extends React.Component {
render() {
return <Container/>
}
}
https://snack.expo.io/SJYuSqIEL
What's wrong with my approach?
UPDATE
Thanks to #Ian Vasco for the answer below - it shows how to get it working with using of functional-style for React component.
But for me the question now is why ()=>funcCaption passed to <MyComponent textData={textCaption} funcData={()=>funcCaption}/> always returns initial value of funcCaption?
But when I change use <MyComponent textData={textCaption} funcData={()=>Math.random()}/> - it shows new generated value every time!
So, the problem in the example you provided is that you are using the constructor which will take a stale value and then you are not assigning again. I like that you started to use React Hooks, so I will refactor your unnecessary class based component. This is the example
import * as React from 'react';
import { Text, View, StyleSheet , Button} from 'react-native';
const MyComponent = (props) => {
React.useEffect(() => {
console.log(`Render MyComponent with static=${props.textData} dynamic=${props.funcData}`)
}, [props])
return (
<Text>static: { props.textData} / dyn: { props.funcData}</Text>
)
}
const Container = () => {
const [textCaption, setTextCaption] = React.useState("Textual")
const [funcCaption, setFuncCaption] = React.useState("Functional");
console.log(`textCaption: ${textCaption}, funcCaption: ${funcCaption}`)
return (
<View style={styles.container}>
<Button title="Change" onPress={()=>{
console.log(`Update dynamic prop`)
setTextCaption((prev) => prev + "!")
setFuncCaption((prev) => prev + "!")
}}/>
<MyComponent textData={textCaption} funcData={funcCaption}/>
</View>
)
}
export default class App extends React.Component {
render() {
return <Container/>
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
}
});
So, as said above, I refactored MyComponent to be a functional component. Is the same logic than Container. If you are not familiar with the useEffect hook, it is a function that will be triggered when the dependencies changes, in this case props, so you can see that the value has changed.
Another thing that could be improved, is that in Container you were setting the state like setState(state + "!"), which can cause problem because state could not be updated with the latest value. The proper way to do it is to take a callback with the previous state like shown in the code.

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;

React-native Flatlist not refreshing data

I'm trying to add data dinamically to my flat list with the following code:
import React, { Component } from 'react';
import { View, Text,FlatList,Button,Alert} from 'react-native';
export default class Lista extends Component {
constructor(props) {
super(props);
this.state = {dados: [
{key:"valeu 1"},
{key:"value 2"}
],
refresh : false
};
}
adicionar(){
this.state.dados.push({key:"value 3"})
this.state.refresh = !this.state.refresh
}
render() {
return (
<View>
<FlatList
data= {this.state.dados}
extraData = {this.state.refresh}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
<Button title="Adicionar"
onPress={this.adicionar.bind(this)}>
</Button>
</View>
);
}
}
I am able to push data to the "dados" object with this.state.dados.push({key:"value 3"}) but when I switch the this.state.refresh variable to true my Flatlist doesn't render the last value.
Thanks in advance
You should never mutate the state directly. Try to call this.setState() function in order to perform better app develompent. It could be the reason why Flatlist is not been re-rendered.
adicionar(){
const { refresh, dados } = this.state;
this.setState({
dados: [...dados, {key:"value 3"}], //that is a better way to update Array-States
refresh: !refresh
})
}
please replace extraData = {this.state.refresh} to extraData = {this.state}
To update a value in state you should always use setState method.
adicionar(){
this.setState(prevState => ({
dados: [...prevState.dados, {key:"value 3"}],
refresh: !prevState.refresh
})
)
}

React Native Redux store dispatches reducers correctly, but doesn't update UI component

Working on a cancer treatment app in react native:
Current functionality: when I move the sliders and change the date on my app, it dispatches changes to the redux store successfully. Unfortunately, my UI doesn't update, even though I am calling the same store from the presentational components that I called for dispatch.
That results in this:
GIF of redux store changing while UI is static
Printing via
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
I tried using subscription, but it seems like this isn't the right way to go about this. Thoughts?
snippets from my code (all in one small file, linked below)
Action
//action
function set_num_treatments(num) {
return {
type: SET_NUM_TREATMENTS,
num: num
}
}
setting the title
SET_NUM_TREATMENTS = "SET_NUM_TREATMENTS"
main reducer
function main_reducer(state = initialState, action) {
switch (action.type) {
case SET_PAGE_VIEW:
return Object.assign({}, state, {
current_page: action.page_of_interest
})
case SET_NUM_TREATMENTS:
return Object.assign({}, state, {
num_treatments: action.num
})
case SET_INTER_TREATMENT_INTERVAL:
return Object.assign({}, state, {
inter_treatment_interval: action.weeks_between_treatments
})
case SET_TREATMENT_START_DATE:
return Object.assign({}, state, {
treatment_start_date: action.date
})
default:
return state
}
return state
}
Here's where I start the store & produce the printing functionality
let store = createStore(main_reducer);
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
here's the presentational components
class TreatmentSettings extends React.Component {
constructor(props){
super(props)
}
render() {
const props = this.props
const {store} = props
const state = store.getState()
return(
<View style={styles.treatment_option_slider_card}>
<Text style={styles.my_font, styles.tx_settings_header}>{state.num_treatments} Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={20} value={12}
onValueChange={(num_treatments) => {store.dispatch(set_num_treatments(num_treatments))}} />
<Text style={styles.my_font, styles.tx_settings_header}>X Weeks Between Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={4} value={2} style={{marginBottom:60}}
onValueChange={(value) => {store.dispatch(set_inter_treatment_interval(value))}}
/>
</View>
)
}
}
These final two components hold the main containers for the app
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={createStore(main_reducer)}>
<AppContainer />
</Provider>
);
}
}
class AppContainer extends React.Component {
constructor(props){
super(props)
}
render(){
return(
<View style={styles.container}>
<TreatmentSettings store={store} />
<Text>footertext</Text>
</View>
)
}
}
the one gist file is here if you want to see it all: https://github.com/briancohn/learning-redux/blob/navigation_addn/App.js
I really appreciate the help—
Thanks in advance!
-Brian
I think the way you are updating the store is fine but there’s something wrong with how your components are listening to the changes.
It seems you meant to use connect from react-redux for the containers to connect to the store. Then you can use mapStateToProps to get the data from the store to pass into the components as props. Check https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options for example.

Refresh overview scene after changing state in another scene with react / redux / react-native-router-flex

Most simplified working example provided in github !!!
I have a simple app to learn building apps with react native and redux. From my understanding if you display data from the redux state in your render method and then values of this state is changed, then the value will be changed as well and react rerenders all components which needs to be rerendered due to the state change.
I have the application available on github: https://github.com/schingeldi/checklist
Its really simple. I have an overview, if you click on the status of an entry, you get to a detailed page. If you click on "Mark xxx" the status in changed in the redux state (according to logs) but its not refreshed in the overview scene.
Basically I have an Overview.js:
class Overview extends Component {
constructor(props) {
super(props);
this.state = {fetching:false};
}
entries() {
// console.log("Overview");
// console.log(this.props);
// console.log(this.props.entries);
return Object.keys(this.props.entries).map(key => this.props.entries[key]);
}
componentDidMount() {
this.setState({fetching:true});
this.props.actions.getEntries()
.then( (res) => {
this.setState({fetching: false});
})
}
handleChange(entryId) {
Actions.detail({id: entryId});
}
render() {
return (
<View>
<ScrollView>
{ !this.state.fetching && this.entries().map((entry) => {
return (
<TouchableHighlight key={entry.id}>
<View >
<Text>{entry.name}</Text>
<TouchableHighlight onPress={(entryId ) => this.handleChange(entry.id)}><Text>{entry.status}</Text></TouchableHighlight>
<Text>---------------------------</Text>
</View>
</TouchableHighlight>
)
}
)
}
{this.state.fetching ? <Text>Searching </Text> : null }
</ScrollView>
</View>
)}
}
function mapStateToProps(state) {
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
When clicking on the Status ( {entry.status} ) I open another Scene Details.js:
class Detail extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentWillMount() {
this.setState({
entry: this.props.entries[this.props.id]
})
}
patchEntry(newStatus) {
console.log("Details: patchEntry with " + this.props.id +" and " + newStatus );
this.props.actions.patchEntry(this.props.id, newStatus);
}
render() {
return (
<View>
<Text>{this.state.entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
}
function mapStateToProps(state) {
console.log(state);
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect( mapStateToProps, mapDispatchToProps)(Detail);
And I have an action and a reducer which are called perfectly fine when one of the TouchableHighlights are pressed. I even see in the logs that the state is changed when outputting the whole state.
But my question is, how do I get the status refreshed on the Overview scene, once I got back (pop) from the Detail scene?
If you need anymore information let me know, but it should be simple to reproduce as I wrote a whole working app. Just clone, npm install and run it.
Thanks a lot for your help.
I did a quick look into your code and here are some suggestions/information.
In you Detail.js file you're setting your state once the component is mounted.
When you update your redux store and get the refreshed props, it won't update your UI because it's reflecting your state, and your state won't get the new value because you're only setting it on componentWillMount method. Check more information here in the docs.
Also it seems it's not very clear for you when to use the React component's state.
In this example, from Detail.js file you don't need the component's state at all. You can compute that value directly from the properties.
Ex:
render() {
const entry = this.props.entries[this.props.id];
return (
<View>
<Text>{entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
You could even do that inside your mapStateToProps function. More info here.
Ex:
function mapStateToProps(state, ownProps) {
return {
entries: state.default.entries,
entry: state.default.entries[ownProps.id],
};
}
It seems your Overview.js file is OK regarding the UI being updated, because it's render method is reflecting the props and not it's state.
UPDATE 06/27
I've just checked your reducers and you may have some fixes to do there as well.
case ENTRY_PATCHING:
let patchedEntries = state.entries;
patchedEntries[action.data.entryId].status = action.data.newStatus;
return {...state,
entries: patchedEntries
}
In this reducer you're mutation your state, and you must not do that. The redux store can't be mutated. You can check more details about it here http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
So, fix example:
case ENTRY_PATCHING:
const patchedEntry = {
...state.entries[action.data.entryId],
status: action.data.newStatus
}
return {
...state,
entries: {
...state.entries,
[action.data.entryId]: patchedEntry,
}
}