Я занимаюсь разработкой приложения для администраторов WooCommerce и нового, чтобы реагировать на нативные программы и использовать реакционную навигацию и выставку. Я хочу по возможности держаться подальше от избыточности, потому что поначалу это сложно изучать, и я думаю, что это может быть бесполезно для этого приложения с простой логикой c.
Как обновить представление, состоящее из плоский список данных списка продуктов, получаемых из сети, при изменении данных продукта путем редактирования экрана продукта с помощью rest api. Я застрял в этой проблеме, потому что я не мог найти способ обновить этот плоский список путем редактирования экрана продукта. Буду признателен за любую помощь.
Полный код доступен на https://github.com/itsgauravjain22/wooadmin
Итак, вот резюме моей структуры кода до сих пор.
Main Navigator (App.js)
- Tab Navigator -> Product Stack Navigator, Order Stack Navigator
- - Product Stack Navigator -> Product List Screen, Single Product Screen, Edit Product Screen
- - Order Stack Navigator -> Order List Screen, Order Details Screen
Экран со списком продуктов, содержащий плоский список, который необходимо обновить, если данные изменяются путем редактирования экрана продукта
import React, { Component } from 'react';
import { StyleSheet, Text, View, FlatList, Image, ActivityIndicator, TouchableOpacity } from 'react-native';
import * as SecureStore from 'expo-secure-store';
export default class ProductsList extends Component {
static navigationOptions = {
headerTitle: 'Products',
headerStyle: {
backgroundColor: '#96588a',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
};
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
base_url: null,
c_key: null,
c_secret: null,
};
}
async componentDidMount() {
await this.getCredentials();
this.fetchProductList();
}
getCredentials = async () => {
const credentials = await SecureStore.getItemAsync('credentials');
const credentialsJson = JSON.parse(credentials)
this.setState({
base_url: credentialsJson.base_url,
c_key: credentialsJson.c_key,
c_secret: credentialsJson.c_secret,
})
}
fetchProductList = () => {
const { base_url, c_key, c_secret, page } = this.state;
const url = `${base_url}/wp-json/wc/v3/products?per_page=20&page=${page}&consumer_key=${c_key}&consumer_secret=${c_secret}`;
this.setState({ loading: true });
setTimeout(() => {
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data: [...this.state.data, ...responseJson],
error: responseJson.error || null,
loading: false,
refreshing: false
});
// this.state.data.forEach(item => console.log(`${item.sku}: ${item.stock_quantity}`))
}).catch((error) => {
this.setState({
error,
loading: false,
refreshing: false
})
});
}, 1500);
};
renderListSeparator = () => {
return (
<View style={{
height: 1,
width: '100%',
backgroundColor: '#999999'
}} />
)
}
renderListFooter = () => {
if (!this.state.loading) return null;
return (
<View style={{
paddingVertical: 20,
}}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
handleRefresh = () => {
this.setState(
{
page: 1,
refreshing: true,
seed: this.state.seed + 1,
},
() => {
this.fetchProductList();
}
)
}
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1,
},
() => {
this.fetchProductList();
}
)
}
render() {
return (
<FlatList
data={this.state.data}
keyExtractor={item => item.id.toString()}
refreshing={this.state.refreshing}
extraData={this.state}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={100}
ItemSeparatorComponent={this.renderListSeparator}
ListFooterComponent={this.renderListFooter}
renderItem={({ item }) =>
<TouchableOpacity onPress={() => {
this.props.navigation.navigate('ProductDetails', {
productId: item.id,
productName: item.name,
productData: item,
base_url: this.state.base_url,
c_key: this.state.c_key,
c_secret: this.state.c_secret
});
}}>
<View style={{ flex: 1, flexDirection: 'row', backgroundColor: 'white' }}>
<View style={{ flex: 1, justifyContent: "center", alignContent: "center" }}>
<Image source={(Array.isArray(item.images) && item.images.length) ?
{ uri: item.images[0].src } :
require('../../../assets/images/blank_product.png')}
onError={(e) => { this.props.source = require('../../../assets/images/blank_product.png') }}
style={{ height: 115, width: 115 }} resizeMode='contain' />
</View>
<View style={{ flex: 2, marginTop: 10, marginBottom: 10, justifyContent: "center" }}>
<View style={{ marginLeft: 10 }}>
<Text style={styles.titleText}>{item.name}</Text>
<Text>SKU: {item.sku}</Text>
<Text>Price: {item.price}</Text>
<Text>Stock Status: {item.stock_status}</Text>
<Text>Stock: {item.stock_quantity}</Text>
<Text>Status: {item.status}</Text>
</View>
</View>
</View>
</TouchableOpacity>
}
/>
);
}
}
const styles = StyleSheet.create({
titleText: {
fontSize: 20,
fontWeight: 'bold',
}
});
Экран сведений о продукте
import React, { Component } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, ScrollView, ActivityIndicator } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
export default class ProductDetails extends Component {
constructor(props) {
super(props);
this.state = { loading: false };
productData = this.props.navigation.getParam('productData');
base_url = this.props.navigation.getParam('base_url');
c_key = this.props.navigation.getParam('c_key');
c_secret = this.props.navigation.getParam('c_secret');
}
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('productName', 'Product'),
headerStyle: {
backgroundColor: '#96588a',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
};
};
getProductImages() {
let productImagesData = [];
productData.images.forEach(item => {
productImagesData.push(
<Image key={`image_${item.id}`} source={(item) ? { uri: item.src } : require('../../../assets/images/blank_product.png')}
onError={(e) => { this.props.source = require('../../../assets/images/blank_product.png') }}
style={{ width: 150, height: 150 }} resizeMode='contain' />
)
});
return <ScrollView horizontal={true}>{productImagesData}</ScrollView>
}
//Get product categories data
getProductCategories() {
let productCategoriesData = [];
productData.categories.forEach(item => {
productCategoriesData.push(
<View key={`category_${item.id}`} style={{ flexDirection: "row", margin: 10, backgroundColor: 'white' }}>
<Text>{item.name ? item.name : null}</Text>
</View>
)
})
return productCategoriesData;
}
//Get product attributes data
getProductAttributes() {
let productAttributesData = [];
productData.attributes.forEach(item => {
let attributesOptions = [];
item.options.forEach((option, index) =>
attributesOptions.push(
<View key={`attribute_${item.id}_option${index}`} style={{ flexDirection: "row", margin: 5, backgroundColor: 'white' }}>
<Text>{option}</Text>
</View>
)
)
productAttributesData.push(
<View key={`attribute_${item.id}`} style={{ marginTop: 10 }}>
<Text>{item.name}</Text>
<ScrollView horizontal={true}>
{attributesOptions}
</ScrollView>
</View>
)
}
)
return productAttributesData;
}
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<View style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }}>
<View style={{ height: 150 }}>
{this.getProductImages()}
</View>
<View style={styles.section}>
<Text style={styles.titleText}>{productData.name}</Text>
<Text>Sku: {productData.sku}</Text>
<Text>Slug: {productData.slug}</Text>
<Text>Total Ordered: {productData.total_sales}</Text>
</View>
<View style={styles.section}>
</ScrollView>
<TouchableOpacity
style={{
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.2)',
alignItems: 'center',
justifyContent: 'center',
width: 60,
height: 60,
position: 'absolute',
bottom: 15,
right: 15,
backgroundColor: '#fff',
borderRadius: 100,
}}
onPress={() => {
this.props.navigation.navigate('EditProduct', {
productId: productData.id,
productName: productData.name,
productData: productData,
base_url: base_url,
c_key: c_key,
c_secret: c_secret
});
}}
>
<Ionicons name="md-create" size={30} color="#96588a" />
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
section: {
marginTop: 15,
marginLeft: 15,
marginRight: 15
},
titleText: {
fontSize: 20,
fontWeight: 'bold',
},
});
Редактировать экран продукта, в котором отображаются данные о продукте изменения с помощью put rest api
import React, { Component } from 'react';
import { StyleSheet, Text, View, Switch, KeyboardAvoidingView, TouchableOpacity, ScrollView, ActivityIndicator, TextInput, Picker, Button } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
export default class EditProduct extends Component {
constructor(props) {
super(props);
productData = this.props.navigation.getParam('productData');
base_url = this.props.navigation.getParam('base_url');
c_key = this.props.navigation.getParam('c_key');
c_secret = this.props.navigation.getParam('c_secret');
this.state = {
loading: false,
error: null,
name: productData.name,
sku: productData.sku,
regularPrice: productData.regular_price,
salePrice: productData.sale_price,
dateOnSaleFrom: productData.date_on_sale_from,
showDateOnSaleFrom: false,
dateOnSaleTo: productData.date_on_sale_to,
showDateOnSaleTo: false,
manageStock: productData.manage_stock,
stockStatus: productData.stock_status,
stockQuantity: productData.stock_quantity,
weight: productData.weight,
length: productData.dimensions.length,
width: productData.dimensions.width,
height: productData.dimensions.height,
productType: productData.type,
virtual: productData.virtual,
downloadable: productData.downloadable,
};
}
static navigationOptions = ({ navigation }) => {
return {
title: 'Edit Product',
headerStyle: {
backgroundColor: '#96588a',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
};
};
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignContent: "center", padding: 20 }}>
<ActivityIndicator color='#96588a' size='large' />
</View>
)
}
return (
<KeyboardAvoidingView style={{ flex: 1, flexDirection: 'column', justifyContent: 'center', }} behavior="padding" enabled>
<ScrollView>
<View style={styles.section}>
<Text>Product Name</Text>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={this.handlleProductName}
value={this.state.name}
/>
</View>
<View style={styles.section}>
<Text style={styles.titleText}>Pricing</Text>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Regular Price: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.regularPrice}
onChangeText={this.handleRegularPrice}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Sale Price: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.salePrice}
onChangeText={this.handleSalePrice}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{
flex: 2
}}>
<Text>Sale Date From: </Text>
</View>
<View style={{ flex: 2 }}>
<TouchableOpacity onPress={this.handleShowDateOnSaleFrom}>
<Text>{this.state.dateOnSaleFrom ? new Date(this.state.dateOnSaleFrom).toDateString() : 'Select Date'}</Text>
</TouchableOpacity>
{this.state.showDateOnSaleFrom && <DateTimePicker
value={this.state.dateOnSaleFrom ? new Date(this.state.dateOnSaleFrom) : new Date()}
mode='date'
onChange={this.handleDateOnSaleFrom}
/>}
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{
flex: 2
}}>
<Text>Sale Date To: </Text>
</View>
<View style={{ flex: 2 }}>
<TouchableOpacity onPress={this.handleShowDateOnSaleTo}>
<Text>{this.state.dateOnSaleTo ? new Date(this.state.dateOnSaleTo).toDateString() : 'Select Date'}</Text>
</TouchableOpacity>
{this.state.showDateOnSaleTo && <DateTimePicker
value={this.state.dateOnSaleTo ? new Date(this.state.dateOnSaleTo) : new Date()}
mode='date'
onChange={this.handleDateOnSaleTo}
/>}
</View>
</View>
</View>
<View style={styles.section}>
<Text style={styles.titleText}>Inventory</Text>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Manage Stock: </Text>
</View>
<View style={{ flex: 2 }}>
<Switch
thumbColor={'#96588a'}
trackColor={{ true: '#D5BCD0' }}
value={this.state.manageStock}
onValueChange={this.handleManageStock}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Stock Status: </Text>
</View>
<View style={{ flex: 2 }}>
<Picker
mode='dropdown'
selectedValue={this.state.stock_status}
onValueChange={this.handleStockStatus}
>
<Picker.Item label="In Stock" value="instock" />
<Picker.Item label="Out of Stock" value="outofstock" />
<Picker.Item label="On Backorder" value="onbackorder" />
</Picker>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Stock Quantity: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.stockQuantity ? this.state.stockQuantity.toString() : null}
onChangeText={this.handleStockQuantity}
/>
</View>
</View>
</View>
<View style={styles.section}>
<Text style={styles.titleText}>Shipping</Text>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Weight: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.weight}
onChangeText={this.handleWeight}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Length: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.length}
onChangeText={this.handleLength}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Width: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.width}
onChangeText={this.handleWidth}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Height: </Text>
</View>
<View style={{ flex: 2 }}>
<TextInput
style={{ height: 30, borderBottomColor: 'gray', borderBottomWidth: 1 }}
keyboardType='numeric'
value={this.state.height}
onChangeText={this.handleHeight}
/>
</View>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Virtual: </Text>
</View>
<View style={{ flex: 2 }}>
<Switch
thumbColor={'#96588a'}
trackColor={{ true: '#D5BCD0' }}
value={this.state.virtual}
onValueChange={this.handleVirtual}
/>
</View>
</View>
<View style={{ flex: 1, marginLeft: 10, marginRight: 10, flexDirection: 'row' }}>
<View style={{ flex: 2 }}>
<Text>Downloadable: </Text>
</View>
<View style={{ flex: 2 }}>
<Switch
thumbColor={'#96588a'}
trackColor={{ true: '#D5BCD0' }}
value={this.state.downloadable}
onValueChange={this.handleDownloadable}
/>
</View>
</View>
</View>
</ScrollView>
<Button
title="Submit"
color="#96588a"
onPress={this.handleSubmit}
/>
</KeyboardAvoidingView>
);
}
handleSubmit = () => {
let updatedProductObject = {
"name": this.state.name,
"sku": this.state.sku,
"regular_price": this.state.regularPrice,
"sale_price": this.state.salePrice,
"date_on_sale_from": this.state.dateOnSaleFrom,
"date_on_sale_to": this.state.dateOnSaleTo,
"manage_stock": this.state.manageStock,
"stock_status": this.state.stockStatus,
"stock_quantity": this.state.stockQuantity,
"dimensions": { "weight": this.state.weight, "length": this.state.length, "height": this.state.height },
"type": this.state.productType,
"virtual": this.state.virtual,
"downloadable": this.state.downloadable,
};
const id = productData.id;
const url = `${base_url}/wp-json/wc/v3/products/${id}?consumer_key=${c_key}&consumer_secret=${c_secret}`;
this.setState({ loading: true });
fetch(url, {
method: 'PUT',
body: JSON.stringify(updatedProductObject),
}).then((response) => response.json())
.then((responseJson) => {
this.setState({
error: responseJson.code || null,
loading: false,
});
}).catch((error) => {
this.setState({
error,
loading: false,
})
});
}
}
const styles = StyleSheet.create({
section: {
marginTop: 20,
marginLeft: 20,
marginRight: 20,
},
titleText: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
},
});