I am building a simple gallery management app with React Native.
In gallery page, I called "FetchGallery" function and in there, I got some response.
This is constructor part.
this.state = {
mainGalleryData: [
{
bg_url: '',
country_id: 0
}
],
};
That is the code for response.
_onFetchGalleryBySite = (e) => {
fetch(config.api.getGalleryInfo + '/' + e, {
method: 'GET',
headers: this.state.myHeaders
})
.then((response) => response.json())
.then((responseJSON) => {
console.log('resJSON=>', responseJSON['gallery_list']); // => log is in the below.
responseJSON['gallery_list'].map(item => {
if (item != "") {
let obj = {}
obj.bg_url = item.bg_url
obj.country_id = item.country_id
this.state.mainGalleryData.push(obj)
}
})
})
.catch(err => console.log('_onFetchGalleryInfoErr=>', err))
}
This is the log of fetched data
resJSON => [{ "bg_url": "staff_upload/bgremoval_20201008030228.png", "country_id": "3" },
{ "bg_url":"Guest/1/image/bgremoval_20201004222851.png", "country_id": "3" }]
And in the render() , I used this data in the flatList
That is the code for FlatList
<FlatList
data={mainGalleryData}
renderItem={this.renderGallery}
keyExtractor={(item, index) => index.toString()}
/>
This is the renderGallery.
renderGallery = (item) => (
<TouchableOpacity onPress={() => console.log('itemClicked=>', item)} style={styles.overlay}>
<Image source={newImg} style={styles.newImg} />
{
this.state.gallery_id == item.location_id ?
<Text style={{ fontWeight: '700' }} numberOfLines={1}>{item.item.title}</Text>
:
<Text style={{ fontSize: 12 }} numberOfLines={1}>{item.item.title}</Text>
}
</TouchableOpacity>
)
When I click one of those data, I want to get this data. But the result is different that I expect.
I want: { "bg_url": "staff_upload/bgremoval_20201008030228.png", "country_id": "3" }
result: { "index": 2, "item": {"Guest/1/image/bgremoval_20201004222851.png", "country_id": "3"}}
So I had to get country_id with "item.item.country_id".
I want to get country_id with "item.country_id".
What is this <"index": 2> ?
Before pushing response in state, please clear "mainGalleryData[]"
because default it has 1 item in state which makes index
inappropriate.
Do this
console.log('resJSON=>', responseJSON['gallery_list']);
const responseData = responseJSON['gallery_list'];
const data = this.state.mainGalleryData.concat(responseData); <-- add this line -->
this.state({ mainGalleryData: data });
Related
function ManageData({props, navigation}) {
const [details, setDetails] = useState({
dataList: [],
loading: true,
offset: 1,
totalRecords: 0,
search: '',
});
useEffect(() => {
getData();
}, []);
const getData = async () => {
try {
// console.log('search',details.search);
var params = {};
params = {
'pagination[page]': details.offset,
'pagination[perpage]': 10,
};
if(details?.search?.length > 0){
params['query[search]'] = details?.search;
params['pagination[pages]'] = 30;
params['pagination[total]'] = 293;
}else{
params['query'] = ""
}
const result = await getPayeeDetails(session, params);
// console.log('result',result?.data?.data?.length);
if (result?.data?.data?.length > 0) {
setDetails(prev => ({
...prev,
offset: prev.offset + 1,
dataList: [...prev.dataList, ...result.data.data],
loading: false,
totalRecords: result.data.recordsFiltered,
}));
}
} catch (error) {
console.log('getPayeesError', error);
}
};
const loadMore = () => {
try {
if (details.dataList.length != details.totalRecords) {
setDetails(prev => ({
...prev,
loading: true,
}));
getData();
}
} catch (error) {
console.log('LoadMoreError', error);
}
};
const searchHandler=(data)=>{
try{
console.log('clearData',data);
setDetails(prev => ({
...prev,
dataList:[],
offset:1,
search: data == 'RESET'?"":data,
}));
getData();
}catch(error){
console.log("SearchError",error)
}
}
return (
<BackDropContainer
searchHandler={searchHandler}>
<View style={{backgroundColor: 'white', flex: 1}}>
<FlatList
style={{marginTop: '4%'}}
data={details?.dataList}
renderItem={({item}) => (
<TouchableOpacity onPress={() => showDialog(item)}>
<Item data={item} />
</TouchableOpacity>
)}
onEndReached={loadMore}
keyExtractor={(item, index) => index}
/>
</View>
</BackDropContainer>
);
}
I have a flatlist with searchview in my React Native application. Each time user scrolls to the end of flatlist the loadmore function will be called and also the offset value is increased as 1 to fetch next page from API.
Every time the API results array of 10 data from API so the flatlist will be loaded 10 by 10 for each scroll. When I type some data in searchview the searchHandler function will be called, and there I want to reset the offset as 1 and also need to send typed data to the API.
The issue is searched data and offset is not sending with API whenever I try to search the data. State is not updating properly when searching data.
Note: The data which is types has to be sent along with API whenever user search something.
This is my flatlist:
<FlatList
data={filteredFilmData}
keyExtractor={(item, index) => item.id}
renderItem={renderFilmList}
disableVirtualization={true}
initialNumToRender={50}
maxToRenderPerBatch={50}
ListFooterComponent={openflatSpinner}
onEndReached={closeFlatSpinner}
/>
renderItem:
const renderFilmList = (itemData) => {
return (
<ListTop100
...
filmFavorite={itemData.item.favorite}
...
/>
);
};
ListTop100:
const changeView = button => {
if (buttonView.includes(button)) {
//remove item from favorites.
let toast = Toast.show('The movie removed from favorites!', {
duration: setTimeout(function () {
Toast.hide(toast);
}, 3000),
position: Toast.positions.CENTER,
shadow: true,
animation: true,
hideOnPress: true,
delay: 0,
});
setButtonView([]);
let array = buttonView;
for (var i = 0; i < array.length; i++) {
if (array[i] === button) {
array.splice(i, 1);
}
}
setButtonView(array);
} else {
//add item to favorites.
let toast = Toast.show('The movie added to favorites!', {
duration: setTimeout(function () {
Toast.hide(toast);
}, 3000),
position: Toast.positions.CENTER,
shadow: true,
animation: true,
hideOnPress: true,
delay: 0,
});
const buttons = [...buttonView, button];
setButtonView(buttons);
}
};
const addFavorite = async () => {
const axios = require('axios');
let config = {
method: 'post',
url: `${apiUrl}/movies/doFavorite`,
headers: {
Authorization: `Bearer ${user.token}`,
},
data: {
user: user.id,
movie: filmId,
},
};
await axios(config)
.then(response => {
changeView(filmId);
})
.catch(error => {
console.log(error);
});
};
useEffect(() => {
const favList = [];
setButtonView([]);
if (filmFavorite) {
favList.push(filmId);
}
setButtonView(favList);
}, []);
return (
<View style={styles.screen}>
...
<View style={styles.favIconView}>
<TouchableOpacity onPress={addFavorite}>
{buttonView.includes(filmId) ? (
<FavIcon size={Platform.OS == 'ios' ? ww(0.06) : ww(0.055)} />
) : (
<UnFavIcon size={Platform.OS == 'ios' ? ww(0.06) : ww(0.055)} />
)}
</TouchableOpacity>
...
</View>
);
Favorites Screen:
const addFavorites = filmId => {
let config = {
method: 'post',
url: `${apiUrl}/movies/doFavorite`,
headers: {
Authorization: `Bearer ${user.token}`,
},
data: {
user: user.id,
movie: filmId,
},
};
axios(config)
.then(response => {
//console.log(JSON.stringify(response.data));
// getFavorites();
let fav = favorites;
fav.shift(e => e.id === filmId);
setFavorites(fav);
let toast = Toast.show(`The movie removed from favorites!`, {
duration: setTimeout(function () {
Toast.hide(toast);
}, 1000),
position: Toast.positions.CENTER,
shadow: true,
animation: true,
hideOnPress: true,
delay: 0,
});
getFavorites();
})
.catch(error => {
console.log(error);
});
};
const getFavorites = () => {
let config = {
method: 'post',
url: `${apiUrl}/movies/getFavorites`,
headers: {
Authorization: `Bearer ${user.token}`,
},
data: {
user: user.id,
},
};
axios(config)
.then(response => {
//console.log(JSON.stringify(response.data.data));
setFavorites(response.data.data);
//console.log(favorites);
})
.catch(error => {
console.log(error);
});
};
useEffect(() => {
getFavorites();
}, []);
return (
<ImageBackground
source={require('../assets/images/background.png')}
style={styles.background}>
...
<View style={styles.scroll}>
<ScrollView>
{favorites?.map((item, index) => (
<View key={index}>
...
<View style={styles.favIconView}>
<TouchableOpacity
onPress={() => {
addFavorites(item.id);
}}>
<View style={styles.favoritesIconView}>
<FavIcon size={ww(0.05)} />
</View>
</TouchableOpacity>
</View>
...
))}
</ScrollView>
</View>
)}
</SafeAreaView>
</ImageBackground>
);
};
Problem:
I can add or remove item to favorites list on flatlist but at favorites screen when i remove an item from list, flatlist can not update. That item looks still favourite. I tried onrefresh for flatlist but it doesnt work or i failed. Also i tried :
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
//code
});
return unsubscribe;
}, [navigation]);
but i dont want to refresh page every navigation. Please help me,
thank you from now!
In the screen that I'm trying to put up. I get the ID of a product. The previous screen being a list of products, which come from an API. I get this id thanks to props.data
The console.log ('props', this.props.data) works fine and returns the correct ID to me regardless of the product clicked.
With the ID of this product, I want to find the detailed information of this product (the reference, the family, the price, the stock etc.).
I created this function to call the product detail via my API.
initListData = async () => {
if (parseInt(this.state.productId) > 0) {
let product_data = await getProduct(this.state.productId);
console.log('product_data', product_data)
this.setState({
displayArray: product_data,
loadMoreVisible: (product_data.length >= 15 ? true : false),
currentPage: 2
});
}
};
I think the problem is that the displayArray[] is empty so
let product_data = await getProduct(this.state.productId);
Doesn't work.
And I get this error: Cannot update a component from inside the function body of a different component
Can you explain to me what's wrong ?
Full code
export default class Information extends Component {
constructor(props) {
super(props);
this.state = {
productId: this.props.data,
displayArray: [],
}
console.log('props', this.props.data) // ok, ça fonctionne, on récupère bien l'ID du produit cliqué
};
initListData = async () => {
if (parseInt(this.state.productId) > 0) {
let product_data = await getProduct(this.state.productId);
console.log('product_data', product_data)
this.setState({
displayArray: product_data,
loadMoreVisible: (product_data.length >= 15 ? true : false),
currentPage: 2
});
}
};
async UNSAFE_componentWillMount() {
this.initListData();
}
render() {
console.log('ça c\'est data = ', this.props.data );
console.log('ça c\'est les props =', this.props );
console.log('ça c\'est le state = ', this.state );
return (
<ScrollView contentContainerStyle={{flex: 1}}>
{
this.state.displayArray.map((item, i) => (
<ListItem bottomDivider>
<Icon name='flight-takeoff' />
<ListItem.Content>
<ListItem.Title style={{color: '#d35400'}}>{item.name}</ListItem.Title>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.family")}: {item.family_id}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.reference")}: {item.reference}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.id")}: {item.id}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.cost")}: {item.cost}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.description")}: {item.description}
</ListItem.Subtitle>
<ListItem.Subtitle style={{ color: '#F78400' }}>
{i18n.t("information.stock")}: {item.stock_status}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
))
}
</ScrollView>
);
}
}
getProduct function : [I just have to hide the real url]
export async function getProduct(product_id) {
const abortController = new AbortController();
let user_id = await retrieveProfileUserId();
let lang = await retrieveAppLang();
let access_token = await renewAccessToken();
let result = {};
if (parseInt(product_id) > 0 && access_token != '' && parseInt(user_id) > 0) {
try {
let response = await fetch(
API_URL +
"/products/" + product_id +
"?user_id=" + user_id +
"&society_id=" + API_SOCIETYID +
"&access_token=" + access_token +
"&lang=" + lang,
{
method: "GET",
signal: abortController.signal,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + API_SECRETKEY,
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0"
}
}
)
.then(response => response.json())
.then(responseData => {
if (responseData.status == 200 && responseData.data) {
console.log("getProduct()::success", responseData.data);
result = responseData.data;
} else if (
responseData.status >= 200 && responseData.status <= 404 &&
responseData.data.error && responseData.data.error.length >= 3
) {
// Handle error
throw responseData.data.error;
} else {
throw "error";
}
});
} catch (error) {
//console.log(error);
abortController.abort();
}
}
Try changing a few things around and it should work.
I also don't like setting state directly inside the fn, so I propose the following change:
initListData = async () => {
if (this.props.data != null) {
const productData = await getProduct(this.state.productId);
return productData; // You are missing this, this is vital!
}
};
async componentDidMount() {
const data = await this.initListData(); // don't forget await here if you're using async
if (data.id) {
this.setState((prevState) => ({
displayArray: [...prevState.displayArray, data],
loadMoreVisible: ..., // I don't know what do you want here, because again, you receive an object from your backend, not an array.
currentPage: 2
}));
}
}
I am building a food delivery application, and I would like to know how I can limit the number of checkboxes selected. An example is when entering the subsidiary, it displays a list of products. If I select a pizza, there is an extras section that limits the number of extras you can select, if you want to select more than two and your limit is two it should not allow you
all this with react hooks, I attach a fragment of my component
const ExtrasSelector = ({options = [{}], onPress = () => {}, limit = 0}) => {
const [showOptions, setShowOptions] = useState(true);
const [selectedAmount, setSelectedAmount] = useState(0);
const EXTRA = ' extra';
const EXTRAS = ' extras';
const updatedList = options.map(data => ({
id: data.id,
name: data.name,
price: data.price,
selected: false,
}));
const [itemsList, setItemsList] = useState(updatedList);
const toggleOptions = () => setShowOptions(!showOptions);
useEffect(() => {
}, [selectedAmount]);
// onPress for each check-box
const onPressHandler = index => {
setItemsList(state => {
state[index].selected = !state[index].selected;
onPress(state[index], getSelectedExtras(state));
// Increments or decreases the amount of selected extras
if (state[index].selected) {
setSelectedAmount(prevState => prevState + 1);
} else {
setSelectedAmount(prevState => prevState - 1);
}
return state;
});
};
const getSelectedExtras = extrasArr => {
const selectedExsArr = [];
extrasArr.map(item => {
if (item.selected) {
selectedExsArr.push(item);
}
});
return selectedExsArr;
};
return (
<View>
<View style={styles.container}>
<TouchableOpacity style={styles.row} onPress={toggleOptions}>
<Text style={styles.boldTitleSection}>
Extras {'\n'}
<Text style={titleSection}>
Selecciona hasta {limit}
{limit > 1 ? EXTRAS : EXTRA}
</Text>
</Text>
<View style={styles.contentAngle}>
<View style={styles.contentWrapperAngle}>
<Icon
style={styles.angle}
name={showOptions ? 'angle-up' : 'angle-down'}
/>
</View>
</View>
</TouchableOpacity>
{showOptions ? (
itemsList.map((item, index) => (
<View key={index}>
<CheckBox
label={item.name}
price={item.price}
selected={item.selected}
otherAction={item.otherAction}
onPress={() => {
onPressHandler(index, item);
}}
/>
<View style={styles.breakRule} />
</View>
))
) : (
<View style={styles.breakRule} />
)}
</View>
</View>
);
};
This is a simple react implementation of "checkboxes with limit" behaviour with useReducer. This way the business logic (here the limitation but can be any) is implemented outside of the component in a pure js function while the component itself is just a simple reusable checkbox group.
const { useReducer } = React; // --> for inline use
// import React, { useReducer } from 'react'; // --> for real project
const reducer = (state, action) => {
if (state.checkedIds.includes(action.id)) {
return {
...state,
checkedIds: state.checkedIds.filter(id => id !== action.id)
}
}
if (state.checkedIds.length >= 3) {
console.log('Max 3 extras allowed.')
return state;
}
return {
...state,
checkedIds: [
...state.checkedIds,
action.id
]
}
}
const CheckBoxGroup = ({ data }) => {
const initialState = { checkedIds: [] }
const [state, dispatch] = useReducer(reducer, initialState)
return (
<table border="1">
{data.map(({ id, label }) => (
<tr key={id}>
<td>
<input
onClick={() => dispatch({ id })}
checked={state.checkedIds.includes(id)}
type="checkbox"
/>
</td>
<td>
{label}
</td>
</tr>
))}
</table>
)
};
const data = [
{ id: "1", label: "Mashroom" },
{ id: "2", label: "Ham" },
{ id: "3", label: "Egg" },
{ id: "4", label: "Ananas" },
{ id: "5", label: "Parmesan" },
]
ReactDOM.render(<CheckBoxGroup data={data} />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
i have a like icon and value of status 0 or 1.want to show thumbs up icon if status is 1 and thumbs down if status is 0.and toggle the same when clicked
<View style={{marginTop:10}}><Icon name={itemData.wliststatus===0?"thumbs-up":"thumbs-down"} size={16} type="font-awesome" onPress={() => this.navigateToApp(itemData)}/></View>
Fetched from API
itemdata:{
"userid": "1",
"views": "258",
"wliststatus": 0,
}
<View style={{marginTop:10}}><Icon name={itemData.wliststatus===0?"thumbs-up":"thumbs-down"} size={16} type="font-awesome" onPress={() => this.navigateToApp(itemData)}/></View>
First create state variable that holds whliststatus value and create function that toggle value of state variable.
state = { whliststatus = false }
const handlePress = itemData => this.setState({ whlistatus: !this.state.whlistatus }, () => { this.navigateToApp(itemData)} })
<View style={{marginTop:10}}><Icon name={this.state.whliststatus ? "thumbs-up" : "thumbs-down"} size={16} type="font-awesome" onPress={this.handlePress(itemData)}/></View>
Use state to change items dynamically.
First, in state variable, set your like status value as false.
Remember: the state variable will reside globally in your class
state = {
liked: false
};
Then in componentDidMount, after successfully fetching data, update your status accordingly in state:
componentDidMount() {
fetch(...)
.then(res => res.json())
.then(res => {
...
itemdata:{
"userid": "1",
"views": "258",
"wliststatus": 0,
}
// here update "liked" status in state
this.setState({ liked: itemdata.wliststatus == 0 ? true : false });
...
})
.catch(error => console.log(error));
}
Now, come directly to the render method, get or set the state and data will be handled automatically:
render() {
const { liked } = this.state;
return (
<View style={{marginTop:10}}>
<Icon name={ liked ? "thumbs-up" : "thumbs-down" }
size={16} type="font-awesome"
onPress={() => this.navigateToApp()}/>
</View>
);
}
Then as it seems you might be updating the like in this.navigateToApp function. There in this method you can update your state as:
navigateToApp() {
const { liked } = this.state;
...
// here just switch your liked status
this.setState({ liked: !liked });
...
}