I have a switch in my tab header and
I want to get the value of switch value in my header every time when I toggle the switch. how can I get that value?
const navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
title: "Home",
headerTitleStyle: {
flex: 1,
textAlign: "center",
color: "white",
fontSize: 20,
},
headerTintColor: "white",
headerStyle: {
backgroundColor: "#4169E1",
},
headerRight: (
<Switch
onValueChange={() => params.handleSave()}
value={this.navigation.state.switchValue}
/>
),
};
};
class HomeScreen extends React.Component {
state = { switchValue: false };
componentDidMount() {
this.props.navigation.setParams({ handleSave: this.toggleSwitch });
}
toggleSwitch = (value) => {
//onValueChange of the switch this function will be called
this.setState({ switchValue: value });
//state changes according to switch
//which will result in re-render the text
};
}
I just called this.props.navigation.setParams every time I update the params value in navigation options
toggleSwitch = (value) => {
this.setState({ switchValue: value });
this.props.navigation.setParams({
switchValue: holder,
});
};
Related
I am utterly confused by this. I have a select box component where I have a selected prop. when true I show a checkmark in the box, if false, not. Now the issue that I have is that after three times clicking it doesn't toggle anymore.
The strangest thing is that I do pass a true boolean to the component's 'selected' prop (see logs), but when I make a log of the 'selected' prop in that child component, it says it is false.
Anyone has a clue why this could be different?
Result of the logs seen below
Parent component: Services.tsx
import React, { useReducer } from "react";
import { makeStyles } from "#material-ui/core";
import { ToggleBox } from "components/ToggleBox";
const useStyles = makeStyles((theme) => ({
container: {
display: "flex",
},
}));
const servicesReducer = (state, action) => {
switch (action.type) {
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions;
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.push(action.payload.name);
}
return {
...state,
selectedOptions: newSelectedOptions,
};
case "add options":
return {
...state,
};
}
};
export const Services = () => {
const classes = useStyles();
const [state, dispatch] = useReducer(servicesReducer, {
financialPlanning: {
description: "",
minHours: null,
maxHours: null,
minPrice: null,
maxPrice: null,
},
selectedOptions: [],
});
const check = state.selectedOptions.includes("financialPlanning");
console.log("check", check);
console.log("check2", state);
return (
<div className={classes.container}>
<ToggleBox
selected={check}
onClick={() => {
console.log("click");
dispatch({
type: "toggle option",
payload: { name: 'financialPlanning' },
});
}}
title="Financiële planning"
>
Hey
</ToggleBox>
</div>
);
};
child component: ToggleBox.tsx
import React from 'react';
import { Box, Card, CardContent, Typography } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
import RadioButtonUncheckedIcon from '#material-ui/icons/RadioButtonUnchecked';
import CheckCircleOutlineIcon from '#material-ui/icons/CheckCircleOutline';
import { responsivePadding } from 'helpers';
export interface ToggleBoxProps {
title: string;
description?: string;
rightIcon?: React.ReactElement;
selected: boolean;
focused?: boolean;
children?: React.ReactNode;
onClick?: () => void;
}
const useStyles = makeStyles(theme => ({
root: ({ selected, focused }: ToggleBoxProps) => {
let borderColor = theme.palette.grey[300];
if (focused) {
borderColor = theme.palette.primary.main;
} else if (selected) {
// eslint-disable-next-line prefer-destructuring
borderColor = theme.palette.grey[500];
}
return {
border: `1px solid ${borderColor}`,
height: '100%',
};
},
content: {
height: '90%',
display: 'flex',
flexDirection: 'column',
},
header: {
display: 'flex',
cursor: 'pointer',
flexDirection: 'row',
justifyContent: 'space-between',
paddingBottom: theme.spacing(2),
marginBottom: theme.spacing(2),
borderBottom: `1px solid ${theme.palette.divider}`,
color: theme.palette.text.secondary,
},
title: {
flex: 1,
marginLeft: theme.spacing(2),
},
}));
export const ToggleBox: React.FC<ToggleBoxProps> = (
props: ToggleBoxProps,
) => {
console.log('props toggleBox', props);
const { title, description, rightIcon, selected, children, onClick } = props;
console.log('selected check prop Togglebox', selected);
const classes = useStyles(props);
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<Box className={classes.header} onClick={onClick}>
{selected ? <CheckCircleOutlineIcon /> : <RadioButtonUncheckedIcon />}
<Typography className={classes.title} color='textSecondary'>
{title}
</Typography>
{rightIcon}
</Box>
<Typography variant='body2' color='textSecondary'>
{description}
</Typography>
{selected && children}
</CardContent>
</Card>
);
};
You seem to be mutating your state when adding a value to the selectedOptions array. .push mutates an existing array in-place.
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions; // <-- saved reference to state
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.push(action.payload.name); // <-- mutation!
}
return {
...state,
selectedOptions: newSelectedOptions,
};
In either case of adding or removing you necessarily need to return a new array reference. You can use Array.prototype.concat to add a value to an array and return a new array reference.
case "toggle option":
const isCurrentlySelected = state.selectedOptions.includes(
action.payload.name
);
let newSelectedOptions = state.selectedOptions;
if (isCurrentlySelected) {
newSelectedOptions = newSelectedOptions.filter(
(item) => item !== action.payload.name
);
} else {
newSelectedOptions.concat(action.payload.name); // <-- add to and return new array
}
return {
...state,
selectedOptions: newSelectedOptions,
};
I'm trying to update a state from my Redux by triggering an action that calls out an API.
I have tried debugging by using a console log on actions and the reducer to see whether the states are changing.
The API returns an object and stores it into the reducer and it seems to work just fine but the Screen UI didn't re-render or update the reducer state.
I tried using useState React Hook function to test whether the issue comes from my component or the dispatch function and turns out the issue is from Redux dispatch hook.
In this case, when I press on my ToggleSwitch component, the action triggers the API and store the new value to the reducer. But the state on the UI side isn't updating.
Here's my code
reducer:
const light_list = (state: object = {}, action) => {
if (action.type === C.FETCH_ALL_LIGHTS) {
state = action.payload;
return state;
} else if (action.type === C.CHANGE_LIGHT_STATE) {
_.merge(state[action.id].state, action.payload);
return state;
} else {
return state;
}
}
action:
export const UpdateLightState = (lampID, jsondata) => async (dispatch, getState) => {
const state = getState();
const { id }: BridgePairedType = state.pairing_bridge;
const bridge: ConfigurationTypes = state.bridge_list[id];
const response = await axios({
url: `http://${bridge.ipaddress}/api/${bridge.username}/lights/${lampID}/state`,
method: 'PUT',
data: jsondata
});
if (response && response.data) {
var payload = {};
response.data.map((data) => {
let key = Object.keys(data.success)[0].substring(Object.keys(data.success)[0].lastIndexOf('/') + 1);
let value = Object.values(data.success)[0];
payload[key] = value;
})
dispatch({
type: C.CHANGE_LIGHT_STATE,
id: lampID,
payload: payload
})
}
}
Screen UI:
function ControlBulbScreen() {
const { colors } = theme;
const lampID = useNavigationParam('lampID');
const dispatch = useDispatch();
const light: LightTypes = useSelector(state => state.light_list);
const updatelight = useCallback((lampID: string, json: LightUpdateStates) => dispatch(UpdateLightState(lampID, json)), [dispatch]);
const bordercolor = { borderColor: colors.white }
return (
<Block style={styles.container}>
<Block flex={false} center row space="between" style={styles.header}>
<Text h1 googlebold>{light[lampID].name}</Text>
<ToggleSwitch
offColor="#DDDDDD"
onColor={theme.colors.secondary}
onToggle={() => updatelight(lampID, { on: !light[lampID].state.on })}
isOn={light[lampID].state.on}
/>
</Block>
</Block>
)
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: theme.sizes.base * 2,
backgroundColor: theme.colors.background
},
header: {
marginTop: 40
},
textControl: {
textAlign: 'left',
fontSize: 16
},
controlrow: {
marginTop: 20,
marginBottom: 10
},
thumb: {
width: 25,
height: 25,
borderRadius: 25,
borderColor: 'white',
borderWidth: 3,
backgroundColor: theme.colors.secondary,
},
textInput: {
height: 30,
borderBottomWidth: .5,
borderRadius: 0,
borderWidth: 0,
color : theme.colors.white,
textAlign: 'left',
paddingBottom: 10
}
});
export default ControlBulbScreen;
I guess the issue is in your reducer. You should not mutate your state directly. What I suggest is to create a new state instead what you need to return in each case.
The following statement will do the job for you: const newState = {...state}.
Try the following:
const light_list = (state: object = {}, action) => {
if (action.type === C.FETCH_ALL_LIGHTS) {
return action.payload;
} else if (action.type === C.CHANGE_LIGHT_STATE) {
const newState = {...state};
_.merge(newState[action.id].state, action.payload);
return newState;
} else {
return state;
}
}
Read further about Immutable Update Patterns in the documentation.
I hope that helps!
I have a variable in state called isLoading. The idea is to display a loading message while the program is communicating the server, then display the data. However, at ling 24, I get an error:
TypeError: This.setState is not a function (in 'this.setState({ isloadin: false});
import React from "react";
import { StyleSheet, Text, View, AsyncStorage } from "react-native";
var text;
export default class App extends React.Component {
constructor(props) {
super(props);
state = {
isLoading: true
};
}
componentDidMount = () => {
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://1.0.0.0:1337/loadTransactions",
{ UserID: 69 },
function(result) {
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
this.setState({
isLoading: false
});
}
);
});
};
render() {
console.log(this.setState.isLoading);
if (this.setState.isLoading) {
console.log(this.setState.isLoading);
return (
<View style={styles.container}>
<Text>Loading....</Text>
</View>
);
} else {
return (
<View style={styles.container}>
<Text>Hi, {text}</Text>
<Text>Test</Text>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});
To maintain the context of a function as the same context where the function was lexically defined, you have to use an arrow function:
componentDidMount = () => {
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://204.48.23.161:1337/loadTransactions",
{ UserID: 69 },
function(result) {
// ^^^^^^^ use `result => ` here
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
this.setState({
isLoading: false
});
}
);
});
};
this (ref to the instance of class) might not be available inside the context of AsyncStorage. Save this as another variable and use inside:
componentDidMount = () => {
const self = this;
AsyncStorage.getItem("accessToken").then(token => {
postdata(
"http://204.48.23.161:1337/loadTransactions",
{ UserID: 69 },
function(result) {
text = toString(result.Data[1].ID);
text = result.Data[1].Label;
console.log(result.Data[1].Label);
self.setState({
isLoading: false
});
}
);
});
};
I am trying to select buttons and then display how many I selected. Something like this:
I selected 4 buttons and I want the text to show "selected:4". Also if I unselect 2, I want the text to show "selected:2.
This is my code:
class RegionBT extends Component {
static defaultProps = {
onToggle: () => {},
}
state = {
status: [{ toggle: false }],
count: ''
}
handleIncrement = () => {
this.setState({
count: this.state.count + 1
});
}
handleDecrement = () => {
this.setState({
count: this.state.count - 1
});
}
_onPress() {
const newState = !this.state.toggle
this.setState({ toggle: newState })
this.props.onToggle(newState)
{this.handleDecrement}
}
render() {
const { count } = this.state
const { toggle } = this.state
const buttonBG = toggle ? '#6AAAC6' : 'white'
const textColor = toggle ? 'gray' : 'gray'
return (
<TouchableOpacity
onPress={()=>this._onPress()}
style={{width:'70%', height:'70%',backgroundColor:buttonBG, justifyContent:'center',
borderColor:'#E0E0E0', borderWidth:1}}>
<Text style={{color:textColor, textAlign:'center', fontSize:13}}>{this.props.text}</Text>
<Text>{count}</Text>
</TouchableOpacity>
)
}
}
I think I did the code correctly but its not working. Any ideas?
I have a component will use map to render multi checkbox, and each checkbox has a callback function "onPress" get by props, the "onPress" function will setState checked, but now when I click on one checkbox, all checkboxs will be chosed, it cause they all use the same state, the goal I wanna choose each checkbox what I just ckick on, I know I can write many state different "onPress" function for each checkbox, but it looks stupid, I will add more checkbox in the future, What's the best and flexiable way to solve the task?
import React, { Component } from 'react'
import { View } from 'react-native'
import { CheckBox } from 'react-native-elements'
const styles = {
CheckBox: {
borderBottomWidth: 0.3,
borderBottomColor: 'gray'
},
checkBox : {
backgroundColor: "#ffffff",
borderWidth: 0
},
text: {
flex: 0.95,
backgroundColor: "#ffffff"
}
}
const languages = ["中文","英文","日文","韓文"]
class Language extends Component {
constructor(props) {
super(props);
this.state = { checked: false };
}
onPress = () => {
this.setState({ checked: !this.state.checked })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state.checked }
onPress = { this.onPress }
/>
</View>
)
})
}
render(){
return(
<View>
{ this.renderlanguages() }
</View>
)
}
}
export default Language;
The behavior is choose all checkbox even though I only choose one now.
You can just pass the langauge (note this is probably a typo for language) variable to the function and us it to identify which one is being checked
onPress = (langauge) => {
this.setState({ [langauge]: { checked: !this.state[langauge].checked } })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
//component = { () => {return <TouchableOpacity></TouchableOpacity>}}
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state[langauge].checked }
onPress = { () => this.onPress(langauge) }
/>
</View>
)
})
}