Having this fixed array of objects like this:
export let items = [
{
name: 'package',
subname: 'test'
},
{
name: 'package',
subname: 'test1'
},
{
name: 'pack',
subname: 'test2'
}
]
it is possible to use iterate using each or anything else to get something like this?
<div class='item'>
<div class='name'>package</span>
<div class='subname'>test</span>
<div class='subname'>test1</span>
</div>
<div class='item'>
<div class='name'>pack</span>
<div class='subname'>test2</span>
</div>
A solution would be to merge the items together in an object that has for keys the different names and as value an array of all subnames just like that:
{
"package": [
"test",
"test1"
],
"pack": [
"test2"
]
}
You can do that in a getSubnamesByName function:
const getSubnamesByName = (items) => {
const mergedItems = {}
items.forEach(({name, subname}) => {
if (mergedItems[name]) mergedItems[name].push(subname)
else mergedItems[name] = [subname]
})
return mergedItems
}
Then just store the result in a mergedItems variable that is populated when the component is mounted:
<script>
import { onMount } from 'svelte';
const ITEMS = [ /* ... */ ];
let mergedItems = {}
const getSubnamesByName = (items) => { /* ... */}
onMount(async () => {
mergedItems = getSubnamesByName(ITEMS)
})
</script>
And finally iterate on this object keys and values by using 2 #each blocks:
{#each Object.keys(mergedItems) as name}
<div class='item'>
<div class='name'>{name}</div>
{#each mergedItems[name] as subname}
<div class='subname'>{subname}</div>
{/each}
</div>
{/each}
Have a look at the REPL.
Related
I'm new to Vue, javascript & Web development. Using Vue, I tried to recreate the moviedb app(from Brad's 50 JS Projects in 50 Days course).
I'm getting stuck and can't get the data out of a scope.
I've successfully retrieved data & destructured it.
But how can I get those values out of that scope (out of setMovies function) and use it in the Vue file (html template)?
Here's my code:
I've made the api_key private
<h1>MovieDesk</h1>
<div class="hero">
<!-- Search -->
<div class="search">
<form #submit.prevent="handleSearch">
<input type="text" placeholder="Search here..." />
<button #click="handleSearch">Search</button>
</form>
</div>
</div>
<!-- Movies -->
<div v-if="searchOn">
<SearchedMovies />
</div>
<div v-else>
<MovieList/>
</div>
</template>
<script>
// imports-------------------
import { ref } from "#vue/reactivity";
import MovieList from "../components/MovieList.vue";
import SearchedMovies from "../components/SearchedMovies.vue";
import { onMounted } from "#vue/runtime-core";
export default {
components: { MovieList, SearchedMovies },
setup() {
const searchOn = ref(false);
const api_url = ref(
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=api_key&page=1"
);
const movies = ref([])
// getting the data ------------------------------
onMounted(() => {
fetch(api_url.value)
.then((res) => res.json())
.then((data) => {
console.log(data);
setMovies(data.results);
});
});
function setMovies(movies) {
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview } = movie;
});
}
return { searchOn, setMovies };
},
};
</script> ```
In your setMovies function, You can set the response in the movies variable and then return that variable from your setup.
function setMovies(apiResponse) {
movies.value = apiResponse
}
return { movies };
Live Demo :
const { ref, onMounted } = Vue;
const App = {
setup() {
const movies = ref([])
onMounted(() => {
const apiResponse = [{
id: 1,
name: 'Movie 1'
}, {
id: 2,
name: 'Movie 2'
}, {
id: 3,
name: 'Movie 3'
}];
setMovies(apiResponse);
})
function setMovies(res) {
movies.value = res;
}
return {
movies
};
}
};
Vue.createApp(App).mount("#app");
<script src="https://unpkg.com/vue#next"></script>
<div id="app">
<pre>{{ movies }}</pre>
</div>
Add 'movies' to the return statement at the bottom of your code, then you should be able to render it.
I have the following object:
const myObject = {
property1: ['apple', 'peach'],
property2: ['blue', 'red']
}
What I want to do is to create a list in a Table where I list on each row, the key name and right below, every elements of the corresponding array. Something like:
<li>property1</li>
<li>apple</li>
<li>peach</li>
<li>property2</li>
<li>blue</li>
<li>red</li>
Thank you all in advance.
It's not really clear whether you want a list or a table. But here's a quick example of a bulleted list using your data.
const { useEffect, useState } = React;
const data = {
property1: ['apple', 'peach'],
property2: ['blue', 'red']
};
// Simple function to mock an API response
function mockApi() {
return new Promise(res => {
setTimeout(() => {
res(JSON.stringify(data));
}, 2000);
});
}
// Create a list, and then `map` over the object
// entries. Use the key as a list heading, and then
// `map` over the values of the array to create a new list.
function Example() {
// Initialise state
const [state, setState] = useState(undefined);
// Get the data after two seconds
useEffect(() => {
mockApi()
.then(res => JSON.parse(res))
.then(data => setState(data));
}, []);
// If there is no state return "No data"
if (!state) return <div>No data</div>;
// Otherwise `map` over the object entries
// setting each key as the header, and `mapping`
// over the values array
return (
<ul>
{Object.entries(state).map(([key, arr]) => {
return (
<li>
{key}
<ul>
{arr.map(el => <li>{el}</li>)}
</ul>
</li>
);
})}
</ul>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Additional documentation
map
Object.entries
You can use something like this one. State is here.
const [records, setRecords] = useState(
[
{ id: 1, content: "property1"},
{ id: 2, content: "apple"},
{ id: 3, content: "peach"},
{ id: 4, content: "property2"},
{ id: 5, content: "blue"}
]);
Return method is here.
return (
<>
<ul>
{
records.map(r =>
<li> {r.id + " " + r.content} </li>
)
}
</ul>
</>
);
const obj = {
property1: ['apple', 'peach'],
property2: ['blue', 'red'],
}
const data = [];
Object.entries(obj).forEach(([key, values]) => {
data.push(key)
if (Array.isArray(values)) {
data.push(...values)
}
});
return (
<ul>
{data.map(str => <li>{str}</li>)}
</ul>
)
I'm trying to filter data based on a simple user search input.
I'm not sure if its the way i'm filtering the data, but whenever I input something in the text box, the data disappears. I can see in dev tools that the state of the query is being stored.
Here's my code in my context file. I'm planning on adding additional filters once the search function is fixed, thus the reason for the more complicated code.
import * as React from "react";
const DefaultState = {
cardListings: [],
filter: {}
};
const CardListingsContext = React.createContext(DefaultState);
export const CardListingsConsumer = CardListingsContext.Consumer;
export class CardListingsProvider extends React.Component {
static applyFilter(cards, filter) {
const { query } = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title.indexOf(search) !== -1);
}
return result;
}
state = DefaultState;
componentDidMount() {
fetch("http://localhost:9000/mwBase")
.then(res => res.json())
.then(res => {
this.setState({ cardListings: res });
});
}
updateFilter = filter => {
this.setState({
filter
});
};
render() {
const { children } = this.props;
const { cardListings, filter } = this.state;
const filteredListings = CardListingsProvider.applyFilter(
cardListings,
filter
);
return (
<CardListingsContext.Provider
value={{
allListings: cardListings,
cardListings: filteredListings,
updateFilter: this.updateFilter
}}
>
{children}
</CardListingsContext.Provider>
);
}
}
Here's my input form
<form
className={formClasses}
noValidate
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
>
<p className="mb-1">Refine your results</p>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Search for a card..."
name="query"
value={this.state.query}
onChange={event => this.setState({ query: event.target.value })}
/>
</div>
and where the Filter is being applied on my home page:
<CardListingsProvider>
<CardListingsConsumer>
{function(value) {
const { cardListings, updateFilter } = value;
return (
<>
<Filter updateFilter={updateFilter} />
<div className="columns">
{cardListings.map(item => (
<Card key={item.itemId} card={item} />
))}
</div>
</>
);
}}
</CardListingsConsumer>
</CardListingsProvider>
</div>
Here's example of my dataset:
[
{
itemId: [
"120901386991"
],
title: [
"1952 Topps Mickey Mantle Chase Card Box 18 packs 5 1950s or 1960's cards per box"
],
globalId: [
"EBAY-US"
],
subtitle: [
"3 BX LOT. 1 VINTAGE PK PER 25 BOXES* LOOK 4 1952 MANTLE"
],
primaryCategory: [
{
categoryId: [
"213"
],
categoryName: [
"Baseball Cards"
]
}
],
secondaryCategory: [
{
categoryId: [
"156521"
],
categoryName: [
"Vintage Non-Sport Cards"
]
}
],
galleryURL: [
"https://thumbs4.ebaystatic.com/m/m1mtMB65mAApWQ2EhJy4qWA/140.jpg"
],
viewItemURL: [
"https://rover.ebay.com/rover/1/711-53200-19255-0/1?ff3=2&toolid=10044&campid=5338164673&customid=watchbask&lgeo=1&vectorid=229466&item=120901386991"
],
paymentMethod: [
"PayPal"
],
autoPay: [
"true"
],
location: [
"USA"
],
country: [
"US"
],
shippingInfo: [
{
shippingServiceCost: [
{
#currencyId: "USD",
__value__: "0.0"
}
],
shippingType: [
"Free"
],
shipToLocations: [
"Worldwide"
],
expeditedShipping: [
"false"
],
oneDayShippingAvailable: [
"false"
],
handlingTime: [
"1"
]
}
],
sellingStatus: [
{
currentPrice: [
{
#currencyId: "USD",
__value__: "118.0"
}
],
convertedCurrentPrice: [
{
#currencyId: "USD",
__value__: "118.0"
}
],
sellingState: [
"Active"
],
timeLeft: [
"P10DT14H59M31S"
]
}
],
listingInfo: [
{
bestOfferEnabled: [
"false"
],
buyItNowAvailable: [
"false"
],
startTime: [
"2012-04-23T16:52:17.000Z"
],
endTime: [
"2019-10-23T16:52:17.000Z"
],
listingType: [
"FixedPrice"
],
gift: [
"false"
],
watchCount: [
"443"
]
}
],
returnsAccepted: [
"false"
],
condition: [
{
conditionId: [
"1000"
],
conditionDisplayName: [
"Brand New"
]
}
],
isMultiVariationListing: [
"false"
],
pictureURLLarge: [
"https://i.ebayimg.com/00/s/NTAwWDMxNA==/z/sT8AAOSw62VZv9qQ/$_1.JPG"
],
topRatedListing: [
"false"
]
},
In your case title is an array of string. If it is supposed to contain only one element. You can change your filter function from
result.filter(item => item.title.indexOf(search) !== -1);
to
result.filter(item => item.title[0].indexOf(search) !== -1);
If the title array contains multiple items, You could do use Array.some
result.filter(item =>
item.title.some(eachTitle => {
return eachTitle.indexOf(search) !== -1
})
)
And if you need case insensitive filter, you might need to change the filter function on that aspect too.
const search = query.toLowerCase();
result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);
Looks like the code snippet you have posted might not be complete. I see some unbalanced parentheses for applyFilter Function in your Provider component.
static applyFilter(cards, filter) {
const { query } = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title.indexOf(search) !== -1);
}
state = DefaultState;
Also I'm wondering why would you need a setTimeout to call setState function in Filter component. The below
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
You can get rid of that as well.
I have made some edits to complete applyFilter function to return the filtered data. Please have a look at the below code and Run Code Snippet to see the code in action. Hope this helps!
// Provider Class
const DefaultState = {
cardListings: [],
filter: {}
};
const CardListingsContext = React.createContext(DefaultState);
const CardListingsConsumer = CardListingsContext.Consumer;
class CardListingsProvider extends React.Component {
static applyFilter(cards, filter) {
const {
query
} = filter;
let result = cards;
if (query) {
const search = query.toLowerCase();
result = result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);
}
return result;
}
state = DefaultState;
componentDidMount() {
Promise.resolve([
{
itemId: ['1'],
title: ['Apple']
},
{
itemId: ['2'],
title: ['Orange']
},
{
itemId: ['3'],
title: ['Peach']
}
]).then(res => {
this.setState({
cardListings: res
});
});
}
updateFilter = filter => {
this.setState({
filter
});
};
render() {
const {
children
} = this.props;
const {
cardListings,
filter
} = this.state;
const filteredListings = CardListingsProvider.applyFilter(
cardListings,
filter
);
return ( <
CardListingsContext.Provider value = {
{
allListings: cardListings,
cardListings: filteredListings,
updateFilter: this.updateFilter
}
} >
{
children
}
</CardListingsContext.Provider>
);
}
}
class Filter extends React.Component {
state = { query: "" };
render() {
return (
<form
noValidate
onChange={() =>
setTimeout(() => this.props.updateFilter(this.state), 0)
}
>
<p className="mb-1">Refine your results</p>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Search for a card..."
name="query"
value={this.state.query}
onChange={event => this.setState({ query: event.target.value })}
/>
</div>
</form>
);
}
}
class Home extends React.Component {
render() {
return (
<div>
<CardListingsProvider>
<CardListingsConsumer>
{function(value) {
const { cardListings, updateFilter } = value;
return (
<React.Fragment>
<Filter updateFilter={updateFilter} />
<div className="columns">
{cardListings.map(item => (
<div key={item.itemId}>{JSON.stringify(item)}</div>
))}
</div>
</React.Fragment>
);
}}
</CardListingsConsumer>
</CardListingsProvider>
</div>
);
}
}
ReactDOM.render( <Home /> , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am using react-table and need to create subrows with the data structure below. I have successfully created subrows for each object in the data array. However, each object in the data array contains another array "types."
How would i go about getting each row to list the "type" names as subrows?
My code so far is below:
Table:
import React from 'react';
import ReactTable from 'react-table';
const Table = (props) => {
const subComponent = row => {
return (
<div>
Names of "types" here respectively for each object in data array
(no column headers or anything needed)
</div>
);
};
return (
<ReactTable data={ props.data }
columns={ props.columns }
SubComponent={ subComponent } />
);
};
export default Table;
Data structure:
const data = [
{
id: '12345',
name: 'sports',
types: [
{
name: 'basketball',
id: '1'
},
{
name: 'soccer',
id: '2'
},
{
name: 'baseball',
id: '3'
}
]
},
{
id: '678910',
name: 'food',
types: [
{
name: 'pizza',
id: '4'
},
{
name: 'hamburger',
id: '5'
},
{
name: 'salad',
id: '6'
}
]
}
];
You can rewrite the getSubRows method on useTable optios.
Something like this:
const getSubRows = useCallback((row) => {
return row.types || [];
}, []);
Here is a good example on how to do it https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/archives/v6-examples/react-table-sub-components
From my best guess, your code will look like this:
import React from 'react';
import ReactTable from 'react-table';
const Table = (props) => {
const subComponent = row => {
return (
<div>
row.Original.types.map((type, idx) => (
<div>{{type.id}}</div>
<div>{{type.name}}</div>
))
</div>
);
};
return (
<ReactTable data={ props.data }
columns={ props.columns }
SubComponent={ subComponent } />
);
};
export default Table;
Vue component won't re-render array items after its value was set externally. State chenges but v-for element is not showing the changes.
I have a component that renders items from array. I also have buttons to change the array length and it works well: '+' adds one line and '-' removes the last line. The problem starts when I set the array data from a fetch method. Data is displayed but '+' and '-' buttons don't work.
Here's a link to codesanbox https://codesandbox.io/s/q9jv524kvw
/App.vue
<template>
<div id="app">
<button #click="downloadTemplate">Load data</button>
<HelloWorld :formData="formData" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
},
data() {
return {
fakeData: {
unloadingContactPersons: [
{
id: this.idGen("unloadingContactPersons"),
value: "123"
},
{
id: this.idGen("unloadingContactPersons"),
value: "1234"
},
{
id: this.idGen("unloadingContactPersons"),
value: "12345"
}
]
},
lengthDependentLoadings: [
"loadingDates",
"loadingAddresses",
"loadingContactPersons"
],
lengthDependentUnloadings: [
"unloadingDates",
"unloadingAddresses",
"unloadingContactPersons"
],
formData: {
unloadingContactPersons: [
{
id: this.idGen("unloadingContactPersons"),
value: ""
}
]
}
};
},
methods: {
idGen(string = "") {
// Math.random should be unique because of its seeding algorithm.
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
// after the decimal.
return (
string +
"_" +
Math.random()
.toString(36)
.substr(2, 9)
);
},
addLine(id) {
console.log("id", id);
const parentName = id.split("_")[0];
const dependentArray = this.lengthDependentLoadings.includes(parentName)
? this.lengthDependentLoadings
: this.lengthDependentUnloadings;
dependentArray.forEach(objName => {
this.formData[objName]
? this.formData[objName].push({
id: this.idGen(objName),
value: ""
})
: null;
});
console.log("--length", this.formData.unloadingContactPersons.length);
},
removeLine(id) {
const parentName = id.split("_")[0];
const dependentArray = this.lengthDependentLoadings.includes(parentName)
? this.lengthDependentLoadings
: this.lengthDependentUnloadings;
dependentArray.forEach(objName => {
this.formData[objName] ? this.formData[objName].pop() : null;
});
console.log("--length", this.formData.unloadingContactPersons.length);
},
downloadTemplate(link) {
// fake fetch request
const getFunctionDummy = data =>
new Promise(resolve => setTimeout(resolve.bind(null, data), 1500));
// data setter
getFunctionDummy(this.fakeData).then(result => {
// set our data according to the template data
const templateKeys = Object.keys(result);
const templateData = result;
this.formData = {};
templateKeys.forEach((key, index) => {
let value = templateData[key];
console.log(value);
if (Array.isArray(value)) {
console.log("array", value);
this.formData[key] = value.map((item, id) => {
console.log("---from-template", item);
return {
id: this.idGen(key),
value: item.value
};
});
} else {
this.formData[key] = {
id: this.idGen(key),
value
};
}
});
});
}
},
mounted() {
// takes id number of item to be added
this.$root.$on("addLine", ({ value }) => {
console.log("---from-mounted", value);
this.addLine(value);
});
// takes id number of item to be removed
this.$root.$on("removeLine", ({ value }) => {
this.removeLine(value);
});
},
beforeDestroy() {
this.$root.$off("addLine");
this.$root.$off("removeLine");
}
};
</script>
/HelloWorld.vue
<template>
<div class="hello">
<div class="form-item">
<div class="form-item__label">
<label :for="formData.unloadingContactPersons"
>Contact person on unload:</label
>
</div>
<div class="form-item__input multiline__wrapper">
<div
class="multiline__container"
#mouseover="handleMouseOver(unloadingContactPerson.id);"
v-for="unloadingContactPerson in formData.unloadingContactPersons"
:key="unloadingContactPerson.id"
>
<span
class="hover-button hover-button__remove"
#click="removeLine(unloadingContactPerson.id);"
><i class="fas fa-minus-circle fa-lg"></i>-</span
>
<input
class="multiline__input"
:id="unloadingContactPerson.id"
type="text"
v-model="unloadingContactPerson.value"
#input="emitFormData"
/>
<span
class="hover-button hover-button__add"
#click="addLine(unloadingContactPerson.id);"
><i class="fas fa-plus-circle fa-lg"></i>+</span
>
</div>
</div>
</div>
</div>
</template>
<script>
import Datepicker from "vuejs-datepicker";
import { uk } from "vuejs-datepicker/dist/locale";
export default {
name: "SubmitForm",
components: {
Datepicker
},
props: {
formData: Object
},
data: () => {
return {
uk,
hoveredItemId: null
};
},
methods: {
emitFormData() {
this.$root.$emit("submitFormData", { value: this.formData });
},
handleMouseOver(id) {
this.hoveredItemId = id;
},
addLine(id) {
// console.log("---add", id);
this.$root.$emit("addLine", {
value: id
});
},
removeLine(id) {
// console.log("---remove", id);
this.$root.$emit("removeLine", {
value: id
});
}
}
};
</script>
Just comment line no 111 of App.vue and it will work.
// this.formData = {}
The problem is that you directly mutating formData object which Vue.js cannot detect. Read more about Array Change detection [List Rendering - Vue.js]