How to attach data from local JS file to state in ReactJS? - javascript

I am having trouble understanding how to access the data from a local JS file. I have read the React documentation up and down, but I'm stuck on this problem. There must be a flaw in my state/prop logic?
import announcementData from "./AnnouncementData.js"
class Detail extends Component {
constructor(props) {
super(props);
this.state = {
announcement: [
{
id: 0,
...
}
async fetchDetails(id) {
let response = announcementData;
this.state.announcement.map(response, (value, key) => {
this.setState({
[value]: key
}).catch(error => {
this.setState({
error: error.message
});
});
});
}
async componentDidMount() {
const { match } = this.props;
await this.fetchDetails(match.params.id);
}
render() {
const detail = {
id: this.state.announcement.id,
title: this.state.announcement.title,
site_id: this.state.announcement.site_id,
content: this.state.announcement.content,
status: this.state.announcement.status,
scheduled_at: this.state.announcement.scheduled_at,
created_at: this.state.announcement.created_at,
categories: this.state.announcement.categories,
members: this.state.announcement.members
};
return (
<div>
<ListGroup>
<Announcement
id={detail.id}
title={detail.title}
site_id={detail.site_id}
content={detail.content}
status={detail.status}
scheduled_at={detail.scheduled_at}
created_at={detail.created_at}
categories={detail.categories}
members={detail.members}
/>
</ListGroup>
</div>
);
}
}
const Announcement = ({id, title, site_id, content, status, scheduled_at, created_at, categories, members}) => {
return (
<div>
<ListGroupItem>ID: {id}</ListGroupItem>
<ListGroupItem>Title: {title}</ListGroupItem>
<ListGroupItem>Site ID: {site_id}</ListGroupItem>
<ListGroupItem>Content: {content}</ListGroupItem>
<ListGroupItem>Status: {status}</ListGroupItem>
<ListGroupItem>Scheduled at: {scheduled_at}</ListGroupItem>
<ListGroupItem>Created at: {created_at}</ListGroupItem>
<ListGroupItem>Categories: {categories}</ListGroupItem>
<ListGroupItem>Members: {members}</ListGroupItem>
</div>
);
};
export default Detail;
I'm trying to publish the details from an array from a local file (for now) to be displayed by the UI. With this minimal code, I am able to display the "Announcement" function with no data, like so:
ID:
Title:
Site ID:
Content:
Status:
Scheduled at:
Created at:
Categories:
Members:
I need to display the actual data coming from the .js file.
It should be a very basic problem but I am a beginner. Any help is appreciated. Thanks!
AnnouncementData.js:
const announcementData = [
{
id: 0,
title: "John Doe",
site_id: "my business",
content: "I have a new business!",
status: true,
created_at: "14/03/2019",
updated_at: "24/04/2019",
categories: [{ id: 0, name: "John Doe" }],
members: [{ id: 1, name: "Jane Doe", photo_url: "jane.png" }]
},
export default announcementData;

You should change your fetchDetails function to something like this:
async fetchDetails(id) {
this.setState({
announcement: announcementData.find(v => v.id === id)
});
}

Related

Type script type from prev

I'm not sure which type to designate here.
"HowitterObject" in setHowitters is data and "...prev' is the continuous addition of data from howitterObject.
interface IhowitterMessage {
message: string;
createAt: number;
id: string;
}
const Home = () => {
const [howitters, setHowitters] = useState<IhowitterMessage[]>([]);
const getHowitters = async () => {
const dbHowitter = await dbService.collection("howitter").get();
dbHowitter.forEach((document) => {
const howitterObject = {
...document.data(),
id: document.id,
};
setHowitters((prev: ???) => [howitterObject, ...prev]); //What should I put in the "prev" type?
});
};
useEffect(() => {
getHowitters();
}, []);
return(
<div>
{howitters.map((howitter: IhowitterMessage) => (
<div key={howitter.id}>
<h4>{howitter.message}</h4>
</div>
))}
</div>
);
};
If you console.log(howitters), it is as follows.
(3) [{...}, {...}, {...}]
0: {message: "no", createAt: 1631367025550, id: "q2d9TTgh36mgFZwMQ5EA"}
1: {createAt: 1631365463319, message: "Good", id: "nCABFp1v3dP73gIbckpp"}
2: {message: "hey", createAt: 1631367021665, id: "dmNBa6C8NwhDQDYDOP36"}
It's similar to your state's type
Because prev is your previous state of howitters so you could put IhowitterMessage[] there too:
setHowitters((prev: IhowitterMessage[]) =>

Vue, firestore: how to display LIVE data after merging collections

See EDIT Below
I have massively improved over my last question, but I am stuck again after some days of work.
Using Vue, Vue-router, Vuex and Vuetify with the Data on Googles Could Firestore
I want to update my data live, but i cannot find a way to do this.
Do i need to restructure, like moving products and categories into one collection?
Or is there any bind or query magic to get this done.
As you can see below, it loads the data on click quite well, but I need the live binding 'cause you could have the page open and someone could sell the last piece (amountLeft = 0). (And a lot of future ideas).
My data structure is the following:
categories: {
cat_food: {
name: 'Food'
parentCat: 'nC'
},
cat_drinks: {
name: 'Food'
parentCat: 'nC'
},
cat_beer: {
name: 'Beer'
parentCat: 'cat_drinks'
},
cat_spritz: {
name: 'Spritzer'
parentCat: 'cat_drinks'
},
}
products: {
prod_mara: {
name: 'Maracuja Spritzer'
price: 1.5
amountLeft: 9
cat: ['cat_spritz']
},
prod_capp: {
name: 'Cappuccino'
price: 2
cat: ['cat_drinks']
},
}
The categories and the products build a tree. The GIF shows me opening the categories down to show a product. You see that it's a product when you have a price tag.
You can see there are two categories that have the same parent (cat_drinks).
The product prod_capp is also assigned to the category and shown side by side to the categories.
I get the data currently this way:
catsOrProd.js
import { catsColl, productsColl } from '../firebase'
const state = {
catOrProducts: [],
}
const mutations = {
setCats(state, val) {
state.catOrProducts = val
}
}
const actions = {
// https://vuefire.vuejs.org/api/vuexfire.html#firestoreaction
async bindCatsWithProducts({ commit, dispatch }, CatID) {
if (CatID) {
// console.log('if CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', CatID).orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', CatID).orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
} else {
// console.log('else CatID: ', CatID)
await Promise.all([
catsColl.where('parentCat', '==', 'nC').orderBy('name', 'asc').get(),
productsColl.where('cats', 'array-contains', 'nC').orderBy('name', 'asc').get()
])
.then(snap => dispatch('moveCatToArray', snap))
}
},
async moveCatToArray({ commit }, snap) {
const catsArray = []
// console.log(snap)
await Promise.all([
snap[0].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
}),
snap[1].forEach(cat => {
catsArray.push({ id: cat.id, ...cat.data() })
})
])
.then(() => commit('setCats', catsArray))
}
}
export default {
namespaced: true,
state,
actions,
mutations,
}
This is a part of my vue file that is showing the data on screen. I have left out the unnecessary parts.
To open everything a have a route with props and clicking on the category sends the router to the next category. (this way i can move back with browser functionality).
Sale.vue
<template>
...........
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)">
....ViewMagic....
</v-col>
............
</template>
<script>
.........
props: {
catIdFromUrl: {
type: String,
default: undefined
}
},
computed: {
// https://stackoverflow.com/questions/40322404/vuejs-how-can-i-use-computed-property-with-v-for
...mapState('catOrProducts', ['catOrProducts']),
},
watch: {
'$route.path'() { this.bindCatsWithProducts(this.catIdFromUrl) },
},
mounted() {
this.bindCatsWithProducts(this.catIdFromUrl)
},
methods: {
leftClickProd(event, catOrProd) {
event.preventDefault()
if (typeof (catOrProd.parentCat) === 'string') { // when parentCat exists we have a Category entry
this.$router.push({ name: 'sale', params: { catIdFromUrl: catOrProd.id } })
// this.bindCatsWithProducts(catOrProd.id)
} else {
// ToDo: Replace with buying-routine
this.$refs.ProductMenu.open(catOrProd, event.clientX, event.clientY)
}
},
}
</script>
EDIT 24.09.2020
I have changed my binding logic to
const mutations = {
setCatProd(state, val) {
state.catOrProducts = val
},
}
const actions = {
async bindCatsWithProducts({ commit, dispatch }, CatID) {
const contain = CatID || 'nC'
const arr = []
catsColl.where('parentCat', '==', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(cat => {
arr.push({ id: cat.id, ...cat.data() })
})
})
productsColl.where('cats', 'array-contains', contain).orderBy('name', 'asc')
.onSnapshot(snap => {
snap.forEach(prod => {
arr.push({ id: prod.id, ...prod.data() })
})
})
commit('setCatProd', arr)
},
}
This works, as the data gets updated when I change something in the backend.
But now i get an object added everytime something changes. As example i've changed the price. Now i get this:
I don't know why, because i have the key field set in Vue. The code for the rendering is:
<v-container fluid>
<v-row
align="center"
justify="center"
>
<v-col
v-for="catOrProduct in catOrProducts"
:key="catOrProduct.id"
#click.prevent="leftClickProd($event, catOrProduct)"
#contextmenu.prevent="rightClickProd($event, catOrProduct)"
>
<div>
<TileCat
v-if="typeof(catOrProduct.parentCat) == 'string'"
:src="catOrProduct.pictureURL"
:name="catOrProduct.name"
/>
<TileProduct
v-if="catOrProduct.isSold"
:name="catOrProduct.name"
... other props...
/>
</div>
</v-col>
</v-row>
</v-container>
Why is this not updating correctly?
From the Vuefire docs, this is how you would subscribe to changes with Firebase only:
// get Firestore database instance
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
created() {
// unsubscribe can be called to stop listening for changes
const unsubscribe = db.collection('todos').onSnapshot(ref => {
ref.docChanges().forEach(change => {
const { newIndex, oldIndex, doc, type } = change
if (type === 'added') {
this.todos.splice(newIndex, 0, doc.data())
// if we want to handle references we would do it here
} else if (type === 'modified') {
// remove the old one first
this.todos.splice(oldIndex, 1)
// if we want to handle references we would have to unsubscribe
// from old references' listeners and subscribe to the new ones
this.todos.splice(newIndex, 0, doc.data())
} else if (type === 'removed') {
this.todos.splice(oldIndex, 1)
// if we want to handle references we need to unsubscribe
// from old references
}
})
}, onErrorHandler)
},
})
I would generally avoid any unnecessary dependencies, but according to your objectives, you can use Vuefire to add another layer of abstraction, or as you said, doing some "magic binding".
import firebase from 'firebase/app'
import 'firebase/firestore'
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()
new Vue({
// setup the reactive todos property
data: () => ({ todos: [] }),
firestore: {
todos: db.collection('todos'),
},
})

Dynamically add navigation items in Vue CoreUI

In my project, I want to add some Ajax loaded menu items to my CoreUI sidebar in Vue. I already found a working solution, but it's kind of hacky and might have timing issues. Therefore I want to ask you, if there is a proper or at least better solution.
I also found this question from a few days ago, but it doesn't have an answer yet.
// main.js
new Vue({
el: '#app',
router,
icons,
template: '<App/>',
components: {
App
},
data: {
clientConfiguration: null
},
created: async function () {
let svcResult = await this.$http.get('Picking/ViewerSettings');
this.clientConfiguration = svcResult.data;
this.$children[0].$children[0].$children[0].$data.nav[0]._children[0].items =
svcResult.data.map(vc => ({
name: vc.name,
to: 'test/' + vc.name,
icon: 'cil-spreadsheet'
}));
}
})
// _nav.js
export default [
{
_name: 'CSidebarNav',
_children: [
{
_name: 'CSidebarNavDropdown',
name: 'Lists',
to: '/test',
icon: 'cil-list-numbered',
items: []
},
// ...
]
}
]
The _nav.js file is just an example of data structure that can be rendered by CRenderFunction component docs
The idea behind CRenderFunction is that you can render components from the Array/Object.
In your case, you have two options:
generate CRenderFunction object on backend,
generate CRenderFunction object on frontend by computed properties, based on data you got from the backend
Here is the example of the second approach:
in template
<CRenderFunction flat :content-to-render="navItems"/>
in script:
//example array that you receive from backend
const menuItems = [
{
name: 'first item',
to: '/first',
icon: 'cil-user'
},
{
name: 'second item',
to: '/second'
},
{
name: 'third item',
to: '/third'
}
]
export default {
computed: {
navItems () {
return [
{
_name: 'CSidebarNav',
_children: this.sidebarNavChildren
}
]
},
sidebarNavChildren () {
return menuItems.map(menuItem => {
return {
_name: 'CSidebarNavItem',
name: menuItem.name,
to: menuItem.to,
icon: menuItem.icon || 'cil-spreadsheet'
}
})
}
}
}
navItems computed property result:
[{"_name":"CSidebarNav","_children": [
{"_name":"CSidebarNavItem","name":"first item","to":"/first","icon":"cil-user"},
{"_name":"CSidebarNavItem","name":"second item","to":"/second","icon":"cil-spreadsheet"},
{"_name":"CSidebarNavItem","name":"third item","to":"/third","icon":"cil-spreadsheet"}
]
}]

How to use my API's response.data and format it into a usable array in React.js

I am using React and the Pokemon API (https://pokeapi.co/) to make a simple web app where the user can search pokemons by name and filter by type.
I successfully implemented the searching for my own data.
constructor() {
super();
this.state = {
contactData: [
{ name: 'Abet', phone: '010-0000-0001' },
{ name: 'Betty', phone: '010-0000-0002' },
{ name: 'Charlie', phone: '010-0000-0003' },
{ name: 'David', phone: '010-0000-0004' }
]
};
}
With the contactData that I have, I successfully search the data that contains the keyword.
render() {
const mapToComponents = (data) => {
//data.sort();
data = data.filter(
(contact) => {
return contact.name.toLowerCase()
.indexOf(this.state.keyword.toLowerCase()) > -1;
}
)
return data.map((contact, i) => {
return (<ContactInfo contact={contact} key={i}/>);
});
};
return(
<div className="Home">
<input
name = "keyword"
placeholder = "Search"
value = { this.state.keyword }
onChange = { this.handleChange }
/>
<div className="info">{ mapToComponents(this.state.contactData)}</div>
</div>
)
}
My question is, I am not sure how to do the same thing with my response data from the Pokemon API. My response data looks like this in the console:
{count: 811, previous: null, results: Array(20), next: "https://pokeapi.co/api/v2/pokemon/?offset=20"}
count
:
811
next
:
"https://pokeapi.co/api/v2/pokemon/?offset=20"
previous
:
null
results
:
Array(20)
0
:
{url: "https://pokeapi.co/api/v2/pokemon/1/", name: "bulbasaur"}
1
:
{url: "https://pokeapi.co/api/v2/pokemon/2/", name: "ivysaur"}
2
:
{url: "https://pokeapi.co/api/v2/pokemon/3/", name: "venusaur"}
3
:
{url: "https://pokeapi.co/api/v2/pokemon/4/", name: "charmander"}
4
:
{url: "https://pokeapi.co/api/v2/pokemon/5/", name: "charmeleon"}
5
:
{url: "https://pokeapi.co/api/v2/pokemon/6/", name: "charizard"}
6
:
{url: "https://pokeapi.co/api/v2/pokemon/7/", name: "squirtle"}
7
:
{url: "https://pokeapi.co/api/v2/pokemon/8/", name: "wartortle"}
8
:
{url: "https://pokeapi.co/api/v2/pokemon/9/", name: "blastoise"}
9
:
{url: "https://pokeapi.co/api/v2/pokemon/10/", name: "caterpie"}
10
:
{url: "https://pokeapi.co/api/v2/pokemon/11/", name: "metapod"}
11
:
{url: "https://pokeapi.co/api/v2/pokemon/12/", name: "butterfree"}
12
:
{url: "https://pokeapi.co/api/v2/pokemon/13/", name: "weedle"}
13
:
{url: "https://pokeapi.co/api/v2/pokemon/14/", name: "kakuna"}
14
:
{url: "https://pokeapi.co/api/v2/pokemon/15/", name: "beedrill"}
15
:
{url: "https://pokeapi.co/api/v2/pokemon/16/", name: "pidgey"}
16
:
{url: "https://pokeapi.co/api/v2/pokemon/17/", name: "pidgeotto"}
17
:
{url: "https://pokeapi.co/api/v2/pokemon/18/", name: "pidgeot"}
18
:
{url: "https://pokeapi.co/api/v2/pokemon/19/", name: "rattata"}
19
:
{url: "https://pokeapi.co/api/v2/pokemon/20/", name: "raticate"}
length
:
20
__proto__
:
Array(0)
__proto__
:
Object
How can format this like the contactData that I've created and display it for searching?
First you need one method to fetch data from API like this:
loadData() {
fetch('https://pokeapi.co/api/v2/pokemon/')
.then(result => result.json())
.then(items => this.setState({ data: items })
}
Then create another method componentDidMount and pass loadData():
componentDidMount() {
this.loadData()
}
From official React documentation:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
More information here: React Components
JSFiddle example:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
this.loadData()
}
// Fetch data from API:
loadData() {
fetch(`https://pokeapi.co/api/v2/pokemon/`)
.then(result => result.json())
.then(items => this.setState({data: items}))
}
render() {
const mapToComponents = data => {
// Your logic...
// Here you can use data...
};
return (
<div>
<h1>Pokemon's:</h1>
<ul>
{this.state.data.results !== undefined ?
this.state.data.results.map((x, i) => <li key={i}>{x.name}</li>)
: <li>Loading...</li>
}
</ul>
<h1>THIS.STATE.DATA:</h1>
<pre>
{JSON.stringify(this.state.data, null, 2)}
</pre>
</div>
);
}
}
ReactDOM.render(
<Data />,
document.getElementById('container')
);
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

How to display list?

How correctly to display the list received through URL in JSON?
Here is an example of a project. If I use a local variable - everything works, if I get the list, it displays an error.
constructor(props) {
super(props);
this.urlNews = "https://api.myjson.com/bins/1nrbo";
this.state = {
news: "",
links: [
{
name: "Имя 1",
url: "http://localhost:1",
use: true
},
{
name: "Имя 2",
url: "http://localhost:2",
use: false
},
{
name: "Имя 3",
url: "http://localhost:3",
use: false
}
]
};
}
getNews() {
fetch(this.urlNews)
.then(response => {
return response.json();
})
.then(json => {
this.setState({
news: json
});
console.log(this.state.news[1]);
});
}
componentDidMount() {
this.getNews();
}
render() {
const { news } = this.state;
return (
<ul>
{news.map((item, index) => <li>1</li>)}
</ul>
);
}
How right?
Because you defined initial value of news as string, define it as an array.
Write it like this:
this.state = {
news: [],
....
fetch will be a asynchronous call and you are fetching in componentDidMount that will get called after initial rendering, so before you get the response, you are trying to use map on string and that is throwing the error.
Check this snippet:
let news = '';
news.map(el => {
console.log(el);
})
You are not using map properly. map does not support object literal. Need to supply it with an array. You need to define the sate as an array and use the following render function.
this.state = {
news: [],
links: [
{
name: "Имя 1",
url: "http://localhost:1",
use: true
},
{
name: "Имя 2",
url: "http://localhost:2",
use: false
},
{
name: "Имя 3",
url: "http://localhost:3",
use: false
}
]
};
The following render function works.
render() {
return (
<ul>
{this.state.news.map(() => <li>1</li>)}
</ul>
);
}

Categories