I'm not 100% sure how to ask this question, and I'm fairly new to react-native. I have a list of categories with a count in them. In PHP I may do something like this:
$purchases['kayaks'] = 0;
$purchases['kayaks']++;
so it increments every time a kayak is sold for this example. or more specifically something like this:
$purchases[$categoryName]++;
I need to take the name of the category based on the user pressing which category they want and add a count to it, then store it in json format in AsyncStorage.
So I know I can do this:
{
"categories": [
{
"kayaks":"0"
}
]
}
And if I import that into "products" I can do products.categories.kayaks to retrieve "0" (or whatever the purchase count is), but I need kayaks to be able to be a variable based on a selection the user makes. so something more like products.categories[categoryName], or however the more optimal way to do that would be in react-native. What is the way (or if there is a more ideal way other than this) to accomplish having different category counts like this?
I hope that makes sense. Thanks in advance!
Here is a basic example written in react-native that uses AsyncStorage to read/write data and manipulates an object. Check out https://facebook.github.io/react-native/docs/asyncstorage for more info about AsyncStorage.
import React from 'react';
import { ActivityIndicator, AsyncStorage, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
input: {
minWidth: 100,
backgroundColor: 'red'
}
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: undefined,
categoryName: 'kayak' // default input value
};
}
componentDidMount() {
AsyncStorage.getItem('data').then(dataAsString => {
// if the data exists lets convert it into an Object we can easily edit
// else default to an empty object
this.setState({ data: dataAsString !== null ? JSON.parse(dataAsString) : {} });
});
}
handleIncrement = () => {
const { data: prevData, categoryName } = this.state;
const prevValue = prevData[categoryName];
// if the category doesn't exist the data lets default to 0
const newValue = prevValue !== undefined ? prevValue + 1 : 0;
this.setState({
data: {
...prevData, // keep all the previous data
[categoryName]: newValue // but set the selected category to the new value
}
});
};
handleSave = () => {
const { data } = this.state;
const dataAsString = JSON.stringify(data); // convert data into a string so AsyncStorage can save it properly
AsyncStorage.setItem('data', dataAsString); // save the data string
};
handleOnChangeText = categoryName => this.setState({ categoryName });
render() {
const { data, categoryName } = this.state;
return (
<View style={styles.container}>
{data === undefined ? (
<ActivityIndicator /> // While data is undefined (AsyncStorage is still doing getItem('data)) show a loading indicator
) : (
<React.Fragment>
<Text>{JSON.stringify(data, null, 2)}</Text>
<TextInput style={styles.input} value={categoryName} onChangeText={this.handleOnChangeText} />
<TouchableOpacity onPress={this.handleIncrement}>
<Text>Add/Increment</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleSave}>
<Text>Save</Text>
</TouchableOpacity>
</React.Fragment>
)}
</View>
);
}
}
export default App;
Note : Its best to use an object to store your data (as Cuong Tran Duc mentions) as you are indexing with category.
Hope this example was helpful.
assuse that your data get from json look like this
const data = {
"categories": [
{
"kayaks":"0"
},
{
"foo":"1"
},
{
"bar":"1"
}
]
}
const categories = data["categories"].reduce((acc, category) => (acc[Object.keys(category)[0]] = Object.values(category)[0], acc), {});
console.log(categories);
you could convert it into an object then access data by categories[categoryName] like you want
change Object.values(category)[0] to +Object.values(category)[0]
if you want to convert string to number
Related
I'm super new to web development but I'm trying to implement a like button to an API array of the mars rover images in react js. The problem I'm having is when I click the like button for one of my images all of the like buttons click. I've tried using id's, classNames, and keys for my button but nothing seems to be working. Any help would be appreciated :)
class Rover extends React.Component {
state = {
loading: true,
images: [],
};
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, loading: false });
console.log(this.state);
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.images) {
return <div>didn't find image</div>
}
return (
<IconContext.Provider value={{ color: '#a9b3c1', size: 64 }}>
<RoverSection>
<RoverWrapper>
<RoverHeading>Mars Rover</RoverHeading>
<div>
{this.state.images.map((image, idx) => (
<div className={`some-image-${idx}`} key={`some-image${idx}`}>
<RoverContainer>
<RoverCard to='/'>
<RoverCardInfo>
<RoverCardID>Photo ID: {image.id}</RoverCardID>
<RoverCardFeatures>
<Img src={image.img_src} />
<RoverCardFeature>{image.rover.name} Rover</RoverCardFeature>
<RoverCardFeature>{image.camera.full_name}</RoverCardFeature>
<RoverCardFeature>{image.earth_date}</RoverCardFeature>
</RoverCardFeatures>
<Button primary id={image.id} className={`some-button-${idx}`} onClick={() => {
this.setState({
isLiked: !this.state.isLiked
})
}}>{this.state.isLiked ? <FaHeart/> : <FaRegHeart/>}</Button>
</RoverCardInfo>
</RoverCard>
</RoverContainer>
</div>
))}
</div>
</RoverWrapper>
</RoverSection>
</IconContext.Provider>
);
}
}
export default Rover
You are mapping through all of the images (you have an array of images); However, you do not have an array for your likes you just have a single boolean value. You need to create an array for your likes of equal size to your image array and then use the same index (you can get the current index in your map (you already did an named it idx))
Your initial like state should be similar to:
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, isLiked:new Array(data.photos.length).fill(false), loading: false });
console.log(this.state);
}
and then in you component
this.setState((current)=>{
const newState={...current};
const isLiked = newState.isLiked;
isLiked[idx] = !isLiked[idx];
return {
...newState,
isLiked:[...isLiked]
}
})
and
{this.state.isLiked[idx] ? <FaHeart/> : <FaRegHeart/>}
lets say this is your image array:
this.state.images = [{id : 0, img : "img1"} ,{id : 1 , img : "img2"} ,{id : 2, img : "img3"} ]
now say you want to like img1 , when you click like on img1, dont you think that you need to only save img1 as liked image. you can do it by modifying the images array object. if you dont want to change data in your original image object,you can keep another array/object map with id and their like status ( you can keep all or you can only push the liked ones.
now when you click on any like button on image , call the function ( also bind it in constructor)
onClick = {(e)=> {this.handleLikeClick(image)}}
and in function handleLikeClick
handleLikeClick(image){
let copyImages = [...this.state.images]
this.state.images.forEach((img) => {
if(img.id === image.id) {
img.liked= !img.liked;
}
});
this.setState({
images :copyImages
})
}
now whenever you are in render , instead of just checking isLIked, you can check the liked property in image object.
I'm implementing a calendar in react native with expo, the problem is that when I want to bring a date from the firebase database to paint it in calendar, it repeats several times, here a picture
View picture
The code is this:
constructor(props) {
super(props);
this.state = {
selected: "",
usuarios: [],
};
}
componentDidMount() {
firebase
.database()
.ref("DatosCli/")
.on("child_added", (data) => {
var datos = data.val();
var usuariosTemp = this.state.usuarios;
datos.key = data.key;
usuariosTemp.push(datos);
this.setState({ usuarios: usuariosTemp });
});
}
cargarDatos = async () => {
var userTemp = new Array();
var data = await firebase.database().ref("/DatosCli").once("value");
data.forEach((child) => {
var user = child.val();
user.key = child.key;
userTemp.push(user);
});
this.setState({ usuarios: userTemp });
};
render() {
return (
<View style={styles.container}>
{this.state.usuarios.map((usuarioTemp) => (
<CalendarList
markedDates={{
[usuarioTemp.date]: {
selected: true,
disableTouchEvent: true,
selectedColor: "orange",
selectedTextColor: "red",
},
}}
/>
))}
</View>
);
}
}
I know that having the map() outside of CalendarList is the reason why it repeats itself several times, how would this be solved then?
The calendar library that i use is: https://github.com/wix/react-native-calendars
You want to have a list of marked dates and not a list of CalendarList components. Right now you are looping through the dates and create a new CalendarList for each of your dates. Instead you want to create an object of dates. Map is part of the array protoype. The prop markedDate seems to require an object though. For that, we need to refactor your code a bit and replace map by forEach for example to construct the object.
Let's split this problem up into two steps. First we create the object of markedDates by using the forEach function and after that we pass it as a prop to CalendarList.
render() {
const markedDates = {};
this.state.usuarios.forEach((usuarioTemp) => {
markedDates[usuarioTemp.date] = {
selected: true,
disableTouchEvent: true,
selectedColor: "orange",
selectedTextColor: "red",
};
};
return (
<View style={styles.container}>
<CalendarList markedDates={markedDates} />
</View>
);
}
So ideally my parent component is mapping through a database and rendering them based on the user's choice. Right now right now the information is being passed correctly and the app is rendering what I need it too (the card component) in the correct amount however it is full of dummy info. (Someone clicks beer, there are three beer types in the database, the app renders three card components full of dummy info).
Here is the parent component:
class Render extends React.Component {
constructor(props) {
super(props);
console.log("Here are your props", props);
}
componentDidMount() {
let options = {
method: 'GET',
url: 'http://localhost:8080/drinks',
};
let drinks = [];
console.log("this is",this);
axios.request(options)
.then( (response) => {
console.log(response);
this.setState({ drinks: response.data })
})
.catch(function (error) {
console.log(error);
});
}
render() {
console.log("this.state is",[this.state])
let stateArray = [this.state]
if (stateArray[0] == null) {
console.log("returning with nothing")
return <div></div>
}
let firstElement = stateArray[0];
let drinks = firstElement.drinks;
let drinkChoice = this.props.reduxState.drinkChoice
console.log("drinkchoice is here" , drinkChoice)
// const props = this.props
console.log("just drinks", drinks)
let drinkInfo = {
type: this.state.drinks.type,
name: this.state.drinks.name,
manufacturer: this.state.drinks.manufacturer,
rating: this.state.drinks.rating,
date: this.state.drinks.date,
description: this.state.drinks.description,
favorite: this.state.drinks.favorite
}
let cardComponents = drinks.map((drink) =>{
if (drink.type === drinkChoice) {
return (<InfoCard props={this.state.drinks} />)
} else {
return <div>Nothing to Report</div>
}})
return (
<div>
<div>{cardComponents}</div>
</div>
)
}
}
export default Render
Now I need it to render the actual database information for each entry. In the child/cardcomponent- I can console.log the props and it will correctly show the right information. It's getting through. But anytime I try to be more specific ( props.name ) it turns to undefined.
I have been at this for days and i'm so confused. The information is right there! I just need to grab it!
Here is the code for the child/card component:
const useStyles = makeStyles(theme => ({
root: {
maxWidth: 345,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
expand: {
transform: 'rotate(0deg)',
marginLeft: 'auto',
transition: theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
},
expandOpen: {
transform: 'rotate(180deg)',
},
avatar: {
backgroundColor: red[500],
},
}));
export default function InfoCard(props) {
const classes = useStyles();
if( props.length <= 0 ) {
return (<div></div>);
} else {
console.log("props are here")
console.log( props )
console.log("props dot name")
console.log ( props.name )
}
props.each(function (drink) {
console.log(drink.name);
});
return (
<Card className={classes.root}>
title = { props.name }
</Card>
);
}
Where have I gone wrong? I feel like i've tried every possible iteration of console.log and drink.name. I'm at the end of my rope.
Thanks for any and all guidance.
sccreengrab of console log
What you're seeing in your log of props is an array of objects. Those objects have names, but the array itself does not, so when you console.log(props.name) it doesn't work and you see undefined. If you try console.log(props[0].name), for instance, you should see a name.
But what's strange here is that props should NOT be an array: it should be an object (whose keys map to the JSX element's attributes). For instance:
<InfoCard name="Bob"/>
would create a props object of:
{name: 'Bob'}
But when you log your props, you see an array, and that means you've somehow/somewhere replaced the actual props object with an array. Without seeing the code where you actually create <Infocard>, I can't speak to the details.
P.S. It might be possible to do this if you did something like:
`<MyComponent {...[{ name: 'Bob']} />`
... but honestly I'm not sure if that even works, and it seems like a bad idea even if it does.
The following code is for a single post, axios fetches the current post ID and stores the result in post array. Keep in mind, only ONE post is stored since this is fetching a single post. The ID and date display properly but if I try to display nested items like rendered content it doesn't work.
Here is what the API json result looks like:
constructor(props) {
super(props);
this.state = {
post: []
};
}
getPost() {
axios
.get('single_post_api')
.then(response => {
this.setState({
post: response.data
});
});
}
componentDidMount() {
this.getPost();
}
render() {
const data = this.state.post;
return (
<View>
<Text>{data.date}</Text>
<Text>{data.slug}</Text>
///Neither of these work///
<Text>{data.content.rendered}</Text>
<Text>{data.content[0]}</Text>
////////////////////////////
</View>
);
}
Try this.
render() {
// If we run into a null, just render it as if we had an empty array.
const posts = (this.state ?? this.state.posts) ? this.state.posts : [];
return (
<View>
(posts.map((post, index) => (
<View key={index}>
<Text>{post.date}</Text>
<Text>{post.slug}</Text>
<Text>{post.content ? post.content.rendered : ""}</Text>
</View>
));
</View>
);
}
Is there any global table option that return the filtred rows? Ignore pagination. All rows matching one or several textFilter?
I need a value in the header showin the average value of the filtred data.
I don't find any on https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html
There is the onDataSizeChange, but it only gives the prop dataSize (nr of rows), also only available when pagination is not used.
Update to second question in comments:
class App extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
data: [...]
filtredData: null
};
};
const factory = patchFilterFactory(filterFactory, (filteredData) => {
this.setState({filteredData}); // causes maximum update exceeded..
});
render() {
return (
<div>
<BootstrapTable
keyField='id'
striped
hover
bootstrap4
data={anbuds}
filter={factory()}
columns={columns}/>
</div>
);
}
}
Kinda.
One way you could do that is by providing a different implementation of the filter prop, and get the data that you need there.
import BootstrapTable from "react-bootstrap-table-next";
import filterFactory, { textFilter } from "react-bootstrap-table2-filter";
function patchFilterFactory(filterFactory, onFilteredData) {
return (...args) => {
const { createContext, options } = filterFactory(...args)
return {
createContext: (...args) => {
const { Provider: BaseProvider, Consumer } = createContext(...args)
const Provider = class FilterProvider extends BaseProvider {
componentDidUpdate() {
onFilteredData(this.data)
}
}
return { Provider, Consumer }
},
options
}
}
}
patchFilterFactory will just sit in between the original filter provider and your code, allowing you to get the data that you need.
How to use it:
function Table() {
const columns = [
{
dataField: "id",
text: "Product ID"
},
{
dataField: "name",
text: "Product Name",
filter: textFilter({
delay: 0
})
},
{
dataField: "price",
text: "Product Price",
filter: textFilter({
delay: 0
})
}
];
const factory = patchFilterFactory(filterFactory, (filteredData) => {
console.log('on filter data', filteredData)
})
return (
<BootstrapTable
keyField="id"
data={props.products}
columns={columns}
filter={factory()}
/>
);
}
I agree, that's far from ideal, but as far as I was able to assess, it may be the only way at the moment.
If you want to change state in the same component, I would recommend:
const [filteredData, setFilteredData] = React.useState([])
const factory = patchFilterFactory(filterFactory, data => {
setFilteredData(prevData => {
if (JSON.stringify(prevData) !== JSON.stringify(data)) {
return data
}
return prevData
})
})
My 2ยข: after some investigation (and intentionally avoiding implementation of filter factory wrapper suggested by #federkun) I found out I can access current filter context of rendered table.
In order to access table properties, I had to add React Ref:
class MyDataTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
this.node = React.createRef();
}
...
render() {
return (
<Card>
<CardBody>
<BootstrapTable
ref={n => this.node = n}
keyField="id"
data={this.state.data}
...
/>
<Button name="click-me" onClick={() => this.handleClick()}>Click me!</Button>
</CardBody>
</Card>
)
}
}
Now when it is possible to reference <BootstrapTable> from code using this.node, I can get to all filtered data (without paging):
// member of MyDataTable component
handleClick() {
console.log(this.node.filterContext.data);
}
Please note that if you access data this way, entries won't be sorted as you see it in the table, so if you want to go really crazy, you can get data filtered and sorted this way:
// member of MyDataTable component
handleClick() {
const table = this.node;
const currentDataView =
(table.paginationContext && table.paginationContext.props.data)
|| (table.sortContext && table.sortContext.props.data) // <- .props.data (!)
|| (table.filterContext && table.filterContext.data) // <- .data (!)
|| this.state.data; // <- fallback
console.log(currentDataView);
}
... however this is becoming pretty wild. Important thing here is to start with paginationContext - anything before it is already sliced into pages. You can check how contexts are put together here: react-bootstrap-table2/src/contexts/index.js.
Nevertheless, this approach is hacky - completely avoiding public API, intercepting context pipeline, reading inputs for each layer. Things may change in newer releases, or there may be issue with this approach I haven't discovered yet - just be aware of that.