How to set state on child component? - react-native

I have a component that renders out a list of buttons, lets call this 'ButtonList'. When one of the buttons is clicked, the event is bubbled up like so:
<ButtonList onButtonPressed={(mins) => { console.log(mins); }} />
In response to this, I want to hide that ButtonList and show another component that is currently hidden. The ButtonList has some state such as "state { visible: true }" that I want to toggle that stops it rendering. How do I make a call to toggle the state of that ButtonList and then also call my other component in this view to also toggle its visible state to show?
Thanks.

swappingComponentsExample = () => {
return (
<View>
{this.state.showButtonList ? (
<ButtonList
onButtonPressed={mins => {
this.setState({showButtonList: false});
console.log(mins);
}}
/>
) : (
<OtherComponent />
)}
</View>
);
};
// Renders both components but passes style override to hide the object
// ButtonList/OtherComponent are not destroyed and recreated using this method
hidingExample = () => {
return (
<View>
<ButtonList
onButtonPressed={mins => {
this.setState({showButtonList: false});
console.log(mins);
}}
style={!this.state.showButtonList && {display: 'none'}}
/>
<OtherComponent
style={this.state.showButtonList && {display: 'none'}}
/>
</View>
);
};

Related

Implementing react-hook-forms and react-select with radio buttons and multi inputs combo

Ready for a complicated one?
Using react-hook-forms and react-select using creatable (user creates multiple inputs on the fly)
I'm trying to implement a form that uses an option on 4 radio buttons, 2 of which reveal multi inputs (inputs that use react-select where the user can create multiple entries, not a dropdown) and trying to keep track of both the radio inputs and the multi inputs in the final useForm() object. I also need to be able to remove them if the user changes their mind or resets the form in total. Right now, the key values of registrationTypes changes when I change radioTwo and enter inputs. I also don't know how to remove user inputs. I'm using Controller to read the entries (although I've heard if you're using native HTML checkbox input, you have to use Register?) . Here's the code:
import styled from 'styled-components'
import Creatable from 'react-select/creatable'
import { Controller } from 'react-hook-form'
import { ErrorRow } from '../util/FormStyles'
import { FormRulesProps } from '../util/formRuleTypes'
import FormError from './FormError'
const registrationTypes = [
{
label: 'Allow anyone with the link to register',
value: 'radioOne',
},
{
label: 'Allow anyone with this email domain to register:',
value: 'radioTwo',
},
{ label: 'Allow anyone with this code to register:', value: 'radioThree' },
{
label: 'Define eligible users manually through eligibilty file.',
value: 'radioFour',
},
]
const RegistrationEligibilty = () => {
const {
control,
reset,
handleSubmit,
formState: { errors },
} = useForm<any>()
const [selectedRadio, setSelectedRadio] = useState({ regType: '' })
const onSubmit = (data: any) => {
console.log(data)
}
const handleSelected = (value: string) => {
setSelectedRadio({ ...selectedRadio, regType: value })
}
return (
<RegistrationEligibiltyContainer>
<form onSubmit={handleSubmit(onSubmit)}>
<FormRow>
<RadioWrapper>
{registrationTypes.map((rt) => (
<Controller
key={rt.value}
control={control}
name="radio"
render={({ field: { onChange, value } }: any) => (
<RadioGroup
// #ts-ignore
value={value}
onChange={(e: any) =>
onChange(e.target.value, handleSelected(rt.value))
}
>
<Radio
name={rt.value}
type="radio"
value={rt.value}
checked={selectedRadio.regType === rt.value}
/>
<TextRow
text={rt.label}
style={{ paddingLeft: '10px' }}
/>
{selectedRadio.regType === 'radioTwo' &&
rt.value === 'radioTwo' && (
<FormRow>
<div style={{ width: '90%' }}>
<Controller
name={rt.value}
control={control}
render={({ field }) => (
<Creatable
{...field}
isMulti
options={field.value}
value={field.value}
placeholder="Select domains"
/>
)}
/>
</div>
</FormRow>
)}
{selectedRadio.regType === 'radioThree' &&
rt.value === 'radioThree' && (
<FormRow>
<div style={{ width: '90%' }}>
<Controller
name={rt.value}
control={control}
render={({ field }) => (
<Creatable
{...field}
isMulti
options={field.value}
value={field.value}
placeholder="Select codes"
/>
)}
/>
</div>
</FormRow>
)}
</RadioGroup>
)}
/>
))}
</RadioWrapper>
</FormRow>
</FormContent>
</form>
</RegistrationEligibiltyContainer>
)
}
export default RegistrationEligibilty
The result looks like this:
https://imgur.com/a/6oGhqRb
I also need to be able to remove them if the user changes their mind or resets the form in total
If the component is unmounted, the value of the component is not included in the result. Make sure your Creatable is unmounted when the relevant radio is not selected

How to create a custom record action button inside a List component with React-Admin?

I'm a totally newbie with React and React-Admin. IMHO, I'm trying to achieve something simple that many people must have already done but I cannot find any kind of tutorial anywhere.
I'd like to add another button to the list of action buttons (show/edit) within each row in a <List> component. This button would archive the record.
My last try looks like the code below.
import React from 'react';
import {
Datagrid,
EmailField,
List,
TextField,
ShowButton,
EditButton,
DeleteButton,
CloneButton,
} from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import ArchiveIcon from '#material-ui/icons/Archive';
const useRowActionToolbarStyles = makeStyles({
toolbar: {
alignItems: 'center',
float: 'right',
width: '160px',
marginTop: -1,
marginBottom: -1,
},
icon_action_button: {
minWidth: '40px;'
},
});
const ArchiveButton = props => {
const transform = data => ({
...data,
archived: true
});
return <CloneButton {...props} transform={transform} />;
}
const RowActionToolbar = (props) => {
const classes = useRowActionToolbarStyles();
return (
<div className={classes.toolbar}>
<ShowButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<EditButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<ArchiveButton {...props} basePath={props.basePath} label="" icon={<ArchiveIcon/>} record={props.record} className={classes.icon_action_button} />
<DeleteButton basePath={props.basePath} label="" record={props.record} className={classes.icon_action_button}/>
</div>
);
};
export const UserList = props => {
return (
<List
{...props}
sort={{ field: 'first_name', order: 'ASC' }}
>
<Datagrid>
<TextField source="first_name"/>
<TextField source="last_name"/>
<EmailField source="email"/>
<RowActionToolbar/>
</Datagrid>
</List>
)
};
Obviously, this code does not work because the <CloneButton> component get rid of the id the record. Moreover, except if I did something wrong - which is totally possible -, it makes a GET request to a create endpoint.
I'm using different routes in my dataProvider (The back end is using Django and Django rest framework). I want to send a PATCH to the detail endpoint, like the <Edit> component does.
I also tried with a <SaveButton>, but it fails too.
Uncaught TypeError: Cannot read property 'save' of undefined
at useSaveContext (SaveContext.js:23)
I guess the <SaveButton> must be within a <SimpleForm>?
I'd like the save behaviour of the <DeleteButton>, i.e. update the record from the list, display the notification that the record has been archived (with the Undo link), send the request to the back end, refresh the list.
Any guidance, directions would be very appreciated.
I don't know that this is a full answer, but felt like more than a comment...
You are trying to archive the existing record, not create a whole new record, right? CloneButton is supposed to be used to create a new record with a new ID (which is why your ID is going away), so you don't want to us it here. note that I've never used CloneButton. it is not fully documented so I could be wrong about its use.
I am thinking that you should use the useRecordContext hook within your Archive button to pull in all of the record's data, including the id; read this little section: https://marmelab.com/react-admin/Architecture.html#context-pull-dont-push
And I don't think transform is what you're looking for here. You will need to use one of the dataProvider hooks, i'm assuming useUpdate: https://marmelab.com/react-admin/Actions.html#useupdate
//first create component
const MyButton = (props: any) => {
const [sendEmailLoading, setSendEmailLoading] =
React.useState<boolean>(false);
const record = useRecordContext(props);
const sendEmail = (id: Identifier) => {
setSendEmailLoading(true)
dataProvider.sendEmail(
"notifications", { id: id })
.then(({ data }: any) => {
if (data && data.status == "success")
notify('Email send success', { type: 'success' });
setSendEmailLoading(false);
refresh();
});
};
return (
<ButtonMUI color='primary' size="small" onClick={() => {
sendEmail(record.id) }}>
{
!record.publish &&(
!sendEmailLoading ? (
translate('resources.notifications.buttons.send')
) : (
<CircularProgress size={25} thickness={2} />
)
)
}
</ButtonMUI>
)
}
//and second add to datagrid list
<Datagrid>
<NumberField source="id" />
<TextFieldRA source="subject" />
<DateField source="date" />
<BooleanField source="publish" />
{/* <EditButton /> */}
<ShowButton />
<MyButton />
</Datagrid>

How to Highlight Updated Items onRefresh of FlatList

I set up a FlatList with an onRefresh function to update the state when the user drags down the screen. It works properly, however I was wondering how I can highlight items in the FlatList that have been updated after the refresh.
Say, for example, I want to change the background for a few seconds for any item in the list that was updated, then return to normal.
<FlatList
data={scores}
renderItem={({item}) => (
<View style={styles.scoreContainer}>
<ScoreRow data={item.away} />
<ScoreRow data={item.home} />
</View>
)}
keyExtractor={item => item.gameID}
refreshing={isRefreshing}
onRefresh={updateScores}
/>
The best I could do was add a useEffect in the ScoreRow component to detect if something changes within that component, but that only allows me to update one component at a time, not the entire View.
const [runUpdate, setRunUpdate] = useState(false)
const [runs, setRuns] = useState(data.R)
useEffect(() => {
if(runs !== data.R) {
setRunUpdate(true)
setRuns(data.R)
setTimeout(() => setRunUpdate(false), 10000)
}
}, [data.R])
I can't figure out how to detect a change on an an item in the View of the FlatList so that I can change the entire View the way I did each component.
You can achieve this by using data of FlatList. You have to make an extra parameter for this.
eg:
//Method to refresh data
_refreshMethod() {
// Do your code to fetch...
...
let newDataArray = data // Data fetch from server or some thing.
let updatedArray = []
newDataArray.map((data, index) => {
data["isNewItem"] = true;
updatedArray.push(data);
});
this.setState({scores: updatedArray})
this._callTimer()
}
// Method to update new item status after a delay
_callTimer() {
setTimeout(function() {
let updatedArray = []
this.state.scores.map((data, index) => {
data["isNewItem"] = false;
updatedArray.push(data);
});
this.setState({scores: updatedArray})
}, 3000); // The time you want to do...
}
Then change the style of row based on the state value.
<FlatList
data={this.state.scores}
renderItem={({item}) => (
<View style={item.isNewItem ? styles.yourNewItemStyle : styles.scoreContainer}>
<ScoreRow data={item.away} />
<ScoreRow data={item.home} />
</View>
)}
keyExtractor={item => item.gameID}
refreshing={isRefreshing}
onRefresh={updateScores}
extraData={this.state}
/>

Flat List gets hidden once the state gets changed in react native

I am having a FlatList view with checkbox for each item.when i press a checkbox the state gets changed from false to true at that time the list gets hidden.Does anyone has come through this strange behaviour.
<FlatList
data={this.state.branches}
renderItem={({ item }) => {
{ tempCheckValues[item._id] = false; }
return (
<ListItem avatar key={item._id}>
<Left>
<CheckBox
checked={item.checked}
onPress={this.toggleCheckbox.bind(this, item._id)}
/>
</Left>
<Body>
<Text>{item.branch_name}</Text>
<Text note>{item.formatted_address}</Text>
</Body>
</ListItem>
);
}
}
/>
Check Box Toggle:
toggleCheckbox = (id) => {
const changedCheckbox = this.state.branches.find((branch) => branch._id === id);
console.log('changedCheckbox', changedCheckbox);
changedCheckbox.checked = !changedCheckbox.checked;
const checkboxes = Object.assign({}, this.state.checkboxes, changedCheckbox);
this.setState({ branches: checkboxes });
}
You are mutating the state can be the reason for the problem. It would better to clone the branches state and update the cloned branches and finally setState.
const clonedBranches = JSON.parse(JSON.stringify(this.state.branches));
const updatedBranch = clonedBranches.find(({_id}) => _id === id)
updatedBranch=!updatedBranches.checked;
this.setState({branches: clonedBranches})
inside toggleCheckbox
this.setState({ branches: checkboxes });
but you are getting data in FlatList with branches
<FlatList
data={this.state.branches}
When you call toggleCheckbox, branches array is updated.
Got it?

How to check checkbox when there is an array in React Native?

I want to get checked friends using checkbox. But I not quite sure how i will achieve it, hope someone can help me.
This is my state:
state = {checked: false}
This is where I want to map array
{this.props.navigation.getParam('friends').map((name, key) => (
<View>
<Text>{name}</Text>
<CheckBox
checked={this.state.checked}
onPress={(val)=>{}}
/>
</View>))}
Note: Or Could someone write me an app/code snippet in snack.expo.io how to get only checked checkbox value
You can write a custom checkbox component
export default class CustomCheckbox extends Component {
constructor(props) {
super(props);
this.state = {
checked: false,
};
}
toggleChange(){
this.setState({checked: !this.state.checked});
}
render() {
return (
<View>
<Text>{this.props.name}</Text>
<CheckBox
checked={this.state.checked}
onPress={() => this.bind.toggleChange(this)}
/>
</View>
);
}
}
and import your CustomCheckbox component
import CustomCheckbox from "your CustomCheckbox.js path"
{this.props.navigation.getParam('friends').map((name, key) => (
<View>
<CustomCheckbox name={name} />
</View>
))}
Your code is pretty good to go, you just need to update a bit. You have following two options:
Your friend's array should have checked key within each containing object, then you can simply do something like this.
{
this.props.navigation.getParam('friends').map((item, key) => (
let {name, checked} = item // item is an object from friends array,the and it have name, checked and other keys
<View>
<Text>{name}</Text>
<CheckBox
checked={checked}
onPress={(val)=>{}}
/>
</View>))
}
Other is you to save the name of the person as key and true/false as the checked state, eg :
toggleCurrentFirendState = (item)=>{
this.setState((prevState)=>{
let {name} = item //get name from clicked friend from the list
return {
...prevState, //used spread operator, so that other states doesn't get mutat.
[name]:!prevState[name] //toogle state of clicked item
}
})
}
//within your render
{
this.props.navigation.getParam('friends').map((item, key) => (
let {name} = item // item is an object from friends array,the and it have name, checked and other keys
<View>
<Text>{name}</Text>
<CheckBox
checked={name ===this.state[name]} //see change
onPress={(val)=>{this.toggleCurrentFirendState(item)}}
/>
</View>))
}