I want to build a todo app with ionic-vue. It currently uses vue 3.
I have this overview (called Lists.vue) where it is possible to click on multiple lists (where tasks should be loaded per list). However, everytime when I click on a list, the same data appears! It is as if the component is being reused but not re rendered/ updated.
I have tried all kinds of solutions. One of them was to apply a watch on the ref that is being changed, however, it does not matter, the end result stays the same. Also tried to give :key to router-link, still does not work.
My Lists.vue
<ion-page>
<ion-content v-if="chunk" class="flex flex-col overflow-auto ion-align-self-center content-wrapper">
<ion-toolbar class="mt-2">
<h1 class="text-4xl pl-5 font-semibold">Lijsten</h1>
</ion-toolbar>
<div v-for="(categoryChunk, index) in chunk.value" :key="index" class="flex flex-wrap w-full flex-row justify-around mt-2">
<div v-for="category in categoryChunk" :key="category.id">
<ion-card class='w-40 sm:w-80'>
<router-link :to="{ name: 'Index', params: {categoryId: category.id} }">
<ion-card-header class="flex">
<ion-icon class="mt-5 text-4xl" color="orange-secondary" :icon="allIcons[category.icon]"></ion-icon>
<div class="m-4">
<p><b>{{ category.title }}</b></p>
<p>Taken: {{ category.tasks }}</p>
</div>
</ion-card-header>
<ion-card-content><div class="line-vert"></div></ion-card-content>
</router-link>
</ion-card>
</div>
</div>
</ion-content>
<div v-else>
<ion-spinner class="centered" color="orange" name="crescent"></ion-spinner>
</div>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button color="orange-secondary" #click="setOpen(true)">
<ion-icon class="text-4xl" color="light" :icon="allIcons.add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-modal
:is-open="isOpenRef"
css-class="my-custom-class"
>
<create-list v-on:on-close="setOpen(false)"></create-list>
</ion-modal>
</ion-page>
</template>
<script>
import { defineComponent, computed, ref, watch, onBeforeMount } from "vue";
import {
IonPage,
IonCard,
IonCardHeader,
IonIcon,
IonCardContent,
IonFab,
IonFabButton,
IonContent,
IonToolbar,
IonModal,
IonSpinner,
} from '#ionic/vue'
import * as allIcons from 'ionicons/icons'
import getCollection from "../../composables/getCollection"
import CreateList from './CreateList'
export default defineComponent ({
components: {
IonPage,
IonCard,
IonCardHeader,
IonIcon,
IonCardContent,
IonFab,
IonFabButton,
IonContent,
IonToolbar,
IonModal,
CreateList,
IonSpinner,
},
setup() {
const { loadCollection } = getCollection();
const chunk = ref()
// Zet modal open/dicht
const isOpenRef = ref(false);
const setOpen = (state) => isOpenRef.value = state;
// Laad alle categorieën uit de database
const reload = () => {
loadCollection('categories').then(data => {
chunk.value = computed(() => {
// Zet de items uit de database om in delen van twee.
const array = [];
const size = 2;
for(let i = 0; i < data.length; i += size) {
array.push(data.slice(i, i+size));
}
return array;
})
})
}
onBeforeMount(() => {
reload();
})
watch(isOpenRef, () =>{
reload()
})
return {
allIcons,
chunk,
isOpenRef,
setOpen,
}
}
})
</script>
My list called Index.vue (maybe I should just call it list.vue or something...)
<template>
<ion-page>
<ion-content v-if="category">
<ion-toolbar>
<div class="flex justify-between">
<h1 class="font-light pl-5">{{ category.title }}</h1>
<ion-icon class="text-2xl pr-5" :icon="closeOutline" #click="redirectBack()"></ion-icon>
</div>
</ion-toolbar>
{{ category }}
</ion-content>
<div v-else>
<ion-spinner class="centered" color="orange" name="crescent"></ion-spinner>
</div>
</ion-page>
</template>
<script>
import { defineComponent, ref } from "vue";
import { closeOutline } from 'ionicons/icons'
import {
IonPage,
IonContent,
IonToolbar,
IonIcon,
IonSpinner,
} from '#ionic/vue'
import { useRoute, useRouter } from "vue-router";
import getValue from "#/composables/getValue";
export default defineComponent ({
components: {
IonPage,
IonContent,
IonToolbar,
IonIcon,
IonSpinner
},
setup() {
const router = useRouter()
const route = useRoute()
const { loadValue } = getValue()
const category = ref()
// redirect terug naar lists indien men op kruisje klikt.
const redirectBack = () => {
return router.push({name: 'Lists'})
}
// Ophalen van data van een lijst.
loadValue('categories', route.params.categoryId).then(data => {
category.value = data
})
return {
closeOutline,
redirectBack,
category,
}
}
})
</script>
My composable function:
import {ref } from "#vue/reactivity";
import { todoFirestore } from "../firebase/config";
const getValue = () => {
const error = ref(null);
const loadValue = async (collectionName: string, id : string) => {
try {
let res = await todoFirestore.collection(collectionName).doc(id)
.get();
if (!res.exists) {
throw Error('Lijst bestaat niet.');
}
return { ...res.data(), id: res.id }
}
catch (err) {
error.value = err.message
}
}
return { error , loadValue }
}
export default getValue;
If someone knows any possible solutions, or what I'm possibly doing wrong, please help! All solutions are very appreciated.
PS: Due to circumstances, I am currently not able to reply very fast, but I assure you that I will reply to your answers :)
Found the answer to my problem! I had to use watchEffect on the loadValue method in order to recall the data from the database. It would seem that Vue (after some research on the internet) wants to reuse components instead of rerendering them, which is more efficient.
The route params were being updated but the key of the component was not, however.
The setup function on Index.vue (the list of tasks)
setup() {
const router = useRouter()
const route = useRoute()
const { loadValue } = getValue()
const category = ref()
// redirect terug naar lists indien men op kruisje klikt.
const redirectBack = () => {
return router.push({name: 'Lists'})
}
// Ophalen van data van een lijst.
const getCategory = () => {
loadValue('categories', route.params.categoryId).then(data => {
category.value = data
})
}
watchEffect(() => {
getCategory()
})
return {
closeOutline,
redirectBack,
category,
}
Related
I'm having trouble creating pagination with vue. My task is to make sure that when you click on the numbers of the buttons, new tasks from jsonplaceholder are loaded.
I have successfully loaded the first and second page. I assume this is related directly to my this.fetchTodos() action. I'm just learning vue and I need help figuring out how to update the data when moving to a new page without loading.
In this case, it is necessary that the url of the page changes (get request). My page state is changing, but posts are not loading when clicking on the third page.
Below is the code of four files that I think will help you understand the situation.
Maybe you will easier with GitHub, please check pagination branch
Thanks in advance for your help! If you have questions or need more information, write in the comments
TodoListView.vue - is starting page, where is todos fetching and rendered on page.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage v-if="todos" text="Список задач"/>
<TitlePage v-else text="Список задач пуст, создайте новую!"/>
<button-bootstrap data-bs-toggle="modal" data-bs-target="#createTodo" css-class="btn-lg btn-primary mt-2 mb-4">Создать задачу</button-bootstrap>
<ModalBootstrap #create="createTodo" :todos="todos" css-id="createTodo"/>
<SearchBootstrap v-if="todos" #search="searchTodo"/>
<div v-if="todos" class="d-flex justify-content-end mt-2">
<button-bootstrap #click.native="setCompletedToAllTodo()" css-class="btn-lg btn-success">Отменить всё как "Выполненные"</button-bootstrap>
</div>
</div>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import TodoList from "#/components/TodoList";
import {mapState, mapActions, mapMutations, mapGetters} from 'vuex'
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import ModalBootstrap from "#/components/UI/ModalBootstrap";
import SearchBootstrap from "#/components/UI/SearchBootstrap";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListView",
components: {
PaginationBootstrap,
SearchBootstrap, ModalBootstrap, TodoList , ButtonBootstrap, TitlePage, ContainerBootstrap},
data: function() {
return {
isShow: false,
}
},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos"
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
}),
createTodo(todo) {
this.$store.commit('todos/addTodo', todo);
},
setCompletedToAllTodo() {
console.log('hello')
this.$store.commit('todos/setCompletedToAllTodo')
},
searchTodo(query) {
this.$store.state.todos.searchQuery = query;
}
},
mounted() {
this.fetchTodos()
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
}
}
</script>
TodoListPaginationView - is second file, where is loading second page and another when click on pagination.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage :text="'Страница №'+ page"/>
<router-link to="/todolist">
<button-bootstrap css-class="btn-lg btn-primary mt-2 mb-4">Вернуться к началу</button-bootstrap>
</router-link>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</div>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import TodoList from "#/components/TodoList";
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListPaginationView",
components: {PaginationBootstrap, TodoList, ButtonBootstrap, TitlePage, ContainerBootstrap},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos",
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
})
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
},
mounted() {
this.fetchTodos()
},
}
</script>
PaginationBootstrap.vue - third file, where is logic for pagination. Ui bootstrap 5 file.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item"><a class="page-link" href="#">Предыдущая</a></li>
<li v-for="pageNumber in totalPages" :key="pageNumber" :class="{'active' : page === pageNumber}" class="page-item">
<span #click="changePage(pageNumber)" class="page-link">{{pageNumber}}</span>
</li>
<li class="page-item"><a class="page-link" href="#">Далее</a></li>
</ul>
</nav>
</template>
<script>
export default {
name: "PaginationBootstrap",
props: {
page: Number,
totalPages: Number
},
methods: {
changePage(pageNumber) {
this.$store.commit('todos/setPage', pageNumber);
if (pageNumber === 1) {
this.$router.push('/todolist')
}
else {
this.$router.push({name: 'todolistPagination', params: {page: pageNumber}})
}
}
}
}
</script>
<style lang="scss" scoped>
.pagination {
.page-item {
.page-link {
cursor: pointer;
}
}
}
</style>
todosModule.js - last file, where is vuex logic for todos.
import axios from "axios";
export const todosModule = {
state: () => ({
todos: [],
page: 1,
limit: 10,
totalPages: 0,
isTodosLoading: false,
searchQuery: '',
}),
mutations: {
setTodos(state, todos) {
state.todos = todos
},
setPage(state, page) {
state.page = page
},
setTotalPages(state, totalPages) {
state.totalPages = totalPages
},
setLoadingTodos(state, bool) {
state.isTodosLoading = bool
},
setCompleted(state, completed) {
const index = state.todos.findIndex(todo => todo.id === completed.id);
state.todos[index].completed = completed.completed
},
setCompletedToAllTodo(state) {
state.todos.map(obj => {
obj.completed = true
})
},
removeTodo(state, id) {
const index = state.todos.findIndex(todo => todo.id === id)
state.todos.splice(index, 1)
},
addTodo(state, todo) {
state.todos.unshift(todo);
},
setTitle(state, tusk) {
const index = state.todos.findIndex(todo => todo.id === tusk.id);
state.todos[index].title = tusk.title
},
setSearchQuery(state, searchQuery) {
state.searchQuery = searchQuery;
}
},
actions: {
async fetchTodos({state, commit}) {
try {
commit('setLoadingTodos' , true)
const response = await axios.get('https://jsonplaceholder.typicode.com/todos', {
params: {
_page: state.page,
_limit: state.limit
}
})
commit('setTotalPages', Math.ceil(response.headers['x-total-count'] / state.limit))
commit('setTodos', response.data)
}
catch (e) {
console.log(e)
}
finally {
commit('setLoadingTodos', false)
}
},
async getCurrentPage({commit}, currentPage) {
try {
commit('setPage', currentPage)
}
catch (e) {
console.log(e);
}
}
},
getters: {
searchedTodos(state) {
return [...state.todos].filter(todo => todo.title.toLowerCase().includes(state.searchQuery.toLowerCase()))
},
},
namespaced: true
}
Okey, I found solution for myself.
Most importent thing is watcher. I added to TodoListPaginationView.vue next code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage :text="'Страница №'+ page"/>
<router-link to="/todolist">
<button-bootstrap css-class="btn-lg btn-primary mt-2 mb-4">Вернуться к началу</button-bootstrap>
</router-link>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</div>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import TodoList from "#/components/TodoList";
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListPaginationView",
components: {PaginationBootstrap, TodoList, ButtonBootstrap, TitlePage, ContainerBootstrap},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos",
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
})
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
},
watch: {
page: function (val) {
if (val) {
this.fetchTodos()
}
},
},
mounted() {
this.fetchTodos();
},
}
</script>
I'm doing a chat widget (a tab system where I have multiple tabs, each tab being a chat) and I'm trying to paint some sort of UX notification every time the user gets a new message from the backend (enduser). In order to do that what I'm trying to do is to emit a custom method from my child component onUpdate lifecycle hook that will trigger the parent component event that will add a class to my parent component in order to add a red border every time the final user send a new message.
This is my child component:
<template>
<div class="flex-col flex-nowrap">
<BaseTranslationBar />
<div id="chat-body" ref="chatBody" class="overflow-y-auto p-2">
<ul id="chatBox" class="mt-4">
<BaseChatMessage
v-for="(chatMessage, index) in chatSession.events"
:chatMessage="chatMessage"
:key="index"
:chatSession="chatSession"
:class="{ 'mt-0': index === 0, 'mt-4': index > 0 }"
/>
</ul>
</div>
<form #submit.prevent="sendMessage" class="chat-form relative mt-6">
<textarea
class="chat-textarea h-[160px] py-4"
placeholder="Add your message here..."
type="text"
v-model="message"
/>
<div
class="chat-input-buttons flex justify-end absolute bottom-0 right-0 pb-4"
>
<button
class="flex flex-col justify-center focus:outline-none focus:ring"
>
<i class="chat-input-button fa-paperclip fas text-blue-500"></i>
</button>
<button
type="submit"
value="Send"
class="flex flex-col justify-center focus:outline-none focus:ring"
>
<i class="chat-input-button fa-paper-plane fas text-blue-500"></i>
</button>
</div>
</form>
<BasePythiaBar class="mt-4" />
<BaseChatButtonRow :chatId="chatSession.id" class="mt-4" />
</div>
</template>
<script>
import { ref, onMounted, onUpdated } from "vue";
import ChatService from "#/services/ChatService.js";
import BaseChatMessage from "#/components/BaseChatMessage.vue";
import BaseTranslationBar from "#/components/BaseTranslationBar.vue";
import BasePythiaBar from "#/components/BasePythiaBar.vue";
import BaseChatButtonRow from "#/components/BaseChatButtonRow.vue";
export default {
emits: ["emittingChatTabUxState"],
components: {
BaseChatMessage,
BaseChatButtonRow,
BasePythiaBar,
BaseTranslationBar,
},
props: {
chatSession: {
type: Object,
default: function () {
return {};
},
},
},
setup(props, { emit }) {
const message = ref("");
const chatBody = ref(null);
onMounted(() => {
scrollChatBodyToEnd();
});
onUpdated(() => {
scrollChatBodyToEnd();
submitChatTabUxState();
});
const scrollChatBodyToEnd = () => {
if (chatBody.value) {
chatBody.value.scrollTop = chatBody.value.scrollHeight;
}
};
const submitChatTabUxState = () => {
emit("emittingChatTabUxState");
};
const sendMessage = () => {
const messageObject = {
aggregateId: props.chatSession.id,
message: message.value,
agentId: props.chatSession.agentId,
skippedValidationErrors: [],
};
ChatService.sendChatMessage(messageObject);
message.value = "";
};
return {
message,
sendMessage,
chatBody,
scrollChatBodyToEnd,
submitChatTabUxState,
};
},
};
</script>
As you can see everytime the dom of this component gets updated I'm emitting a custom event called "submitChatTabUxState" to the parent.
This is the parent component:
<template>
<div>
<ul
class="tag-menu flex space-x-2"
:class="defaultTagMenu ? 'default' : 'historic'"
role="tablist"
aria-label="Tabs Menu"
v-if="tabTitles && tabTitles.length"
>
<li
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
:title="title"
role="presentation"
:class="{ selected: title === selectedTitle }"
>
<a href="#" role="tab" :class="updateChatClassValue"
#emittingChatTabUxState="updateChatClass()">
{{ title }}
</a>
</li>
</ul>
<slot />
</div>
</template>
<script>
import {
ref,
computed,
useSlots,
provide,
watch,
onMounted,
onUpdate,
} from "vue";
export default {
props: {
defaultTagMenu: {
type: Boolean,
default: true,
},
},
setup() {
const updateChatClassValue = ref("");
const slots = useSlots();
const tabTitles = computed(() =>
slots.default()[0].children.map((tab) => tab.props.title)
);
const tabTitlesLength = computed(() => tabTitles.value.length);
let selectedTitle = ref(tabTitles.value[0]);
provide("selectedTitle", selectedTitle);
provide("tabTitles", tabTitles);
watch(tabTitlesLength, (currentValue, oldValue) => {
if (currentValue < oldValue) {
selectedTitle.value = tabTitles.value[0];
}
});
onMounted(() => {
updateChatClass();
timing();
});
onUpdate(() => {
console.log("updating parent component");
});
const timing = () => {
setInterval(() => {
updateChatClassValue.value = "";
}, 2000);
};
const updateChatClass = () => {
console.log("Updating chat class");
updateChatClassValue.value = "chatUpdated";
};
return {
tabTitles,
selectedTitle,
tabTitlesLength,
timing,
updateChatClass,
updateChatClassValue,
};
},
};
</script>
Now what is confusing to me at this point is where to trigger the emitted event coming from my child component in the parent component. I don't have a specific call to action to do this because I will need to trigger the custom event in my parent component every time the child component gets updated so I don't know where should I place something like #emittingChatTabUxState="updateChatClass()". I'm adding it at the <a> tag at the moment but it's not working.
I wish to place a list of posts on my home page instead of having to create a seperate dynamic page. This is my gatsby-node.js file
// DYNAMICALLY CREATE PAGES FOR EACH POST
module.exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions;
const postTemplate = path.resolve('src/templates/news.js');
const postResult = await graphql(`
query {
allContentfulPost {
edges {
node {
slug
}
}
}
}
`);
// Handle errors
if (postResult.errors) {
reporter.panicOnBuild('Error while running GraphQL query.');
return;
}
// Create the pages for each markdown file
postResult.data.allContentfulPost.edges.forEach(({ node }) => {
createPage({
component: postTemplate,
path: `/news/${node.slug}`,
context: {
slug: node.slug,
},
});
});
// PAGINATION FOR BLOG POSTS
const postsResult = await graphql(`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
slug
}
}
}
}
`);
if (postsResult.errors) {
reporter.panicOnBuild('Error while running GraphQL query.');
return;
}
// Create blog-list pages
const posts = postsResult.data.allContentfulPost.edges;
const postsPerPage = 12;
const postNumPages = Math.ceil(posts.length / postsPerPage);
Array.from({ length: postNumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/' : `/news/${i + 1}`,
component: path.resolve('./src/templates/news-list.js'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
postNumPages,
currentPage: i + 1,
},
});
});
};
And this is my news-list.js file
import React from 'react';
import { Link, graphql } from 'gatsby';
import Layout from '../components/layout';
import SEO from '../components/seo';
export const query = graphql`
query ($skip: Int!, $limit: Int!) {
allContentfulPost(sort: { fields: date, order: DESC }, limit: $limit, skip: $skip) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
const NewList = (props) => {
// const { postNumPages } = props.pageContext;
const posts = props.data.allContentfulPost.edges;
return (
<Layout>
<SEO title='News' />
{posts.map(({ node }) => {
const title = node.title || node.slug;
return (
<div className='container mx-auto prose prose-lg'>
<div className='mb-2'>
<Link to={`/posts/${node.slug}`}>
<h3 className='underline font-sans mb-1'>{title}</h3>
</Link>
<div className='flex items-center justify-between'>
<span className='font-mono text-sm'>{node.date}</span>
</div>
</div>
</div>
);
})}
</Layout>
);
};
export default NewList;
I have tried to import the above news-list.js as component from my templates folder into my index.js folder. However I am getting the Error:
TypeError: Cannot read property 'allContentfulPost' of undefined
But if i add path: i === 0 ? '/news' : /news/${i + 1}, into my node file and go to localhost/news i get the list of posts.
But I want them on the home page.. So i thought If I was to just have / it would work turns out no.
How can i get the posts that are listed at LH/news to be displayed on my homepage instead.
Update
New Component after latest answer
import React from 'react';
import { useStaticQuery, graphql, Link } from 'gatsby';
import Layout from '../components/layout';
// import News from '../components/news';
// import NewsList from '../templates/news-list';
export const query = graphql`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
const Index = ({ data }) => {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
companyname
}
}
}
`
);
return (
<Layout>
<section className='c-mt-10'>
<div className=''>
<div className='font-mono md:flex md:justify-between'>
<div className='mb-5'>
<a href={`mailto:hello#${site.siteMetadata.companyname}.co.uk`}>
hello#pfb{site.siteMetadata.companyname}.co.uk
</a>
<br />
<br />
<tel>+44 020 3925 6054</tel>
</div>
<a
href='https://www.google.com/maps/place/Warnford+Court,+29+Throgmorton+St,+London+EC2N+2AT/#51.5154096,-0.0890419,17z/data=!3m1!4b1!4m5!3m4!1s0x48761cacb440b98d:0x9742679143333ff!8m2!3d51.5154096!4d-0.0868479'
target='_blank'
rel='noreferrer'>
<address className='text-right'>
Warnford Court
<br />
29 Throgmorton Street
<br /> London, EC2N 2AT
</address>
</a>
</div>
</div>
<div>
<h2>Company News</h2>
<ul>
{data.allContentfulPost.edges.map(({ node }) => (
<li key={node.title}>
<Link to={node.slug}>{node.title}</Link>
</li>
))}
</ul>
</div>
</section>
</Layout>
);
};
export default Index;
I think you are mixing a lot of concepts.
One thing is the gatsby-node.js queries, useful to create dynamic pages based on dynamic data (from Contentful CMS in your case) based on a parameter (slug in your case).
Another thing is page queries, a way of retrieving data in a top-level components (pages or templates, not components).
If you want to list all your post in your homepage, you just need to create a GraphQL query and loop through the results just like:
const IndexPage = ({ data }) => {
return <Layout>
<ul>
{data.allContentfulPost.edges.map(({node})=> <li key={node.title}><Link to={node.slug}>{title}</Link></li>)}
</ul>
</Layout>
}
export const query = graphql`
{
allContentfulPost(sort: { fields: date, order: DESC }, limit: 1000) {
edges {
node {
title
slug
date(formatString: "MMMM Do, YYYY")
}
}
}
}
`;
When using page queries, your data is stored inside props.data so you can destructure them directly into data.
In your case, you were importing a template inside a page, which doesn't make much sense because you don't have, among other things, the query.
i want to change the language of a quote, from French to English or inverse when i click on "lang button", the quote is generate randomly from an array when the page start.
this is my array:
const quoteData = [
{
quoteEn:
"Three things cannot be long hidden: the sun, the moon, and the truth.",
quoteFr:
"Trois choses ne peuvent pas être cachées longtemps : le soleil, la lune et la vérité.",
author: "Buddha"
},
{
quoteEn:
"We make a living by what we get, but we make a life by what we give.",
quoteFr:
"On vit de ce que l’on obtient. On construit sa vie sur ce que l’on donne.",
author: "Winston Churchill"
}
export default quoteData;
and this is what i tried:
import React, { useState } from "react";
import quoteData from "./components/quoteData";
import "./App.css";
import { ReactComponent as Github } from "./icons/github-brands.svg";
function App() {
const getRandomQuotes = () => {
const randNumb = Math.floor(Math.random() * quoteData.length);
return quoteData[randNumb];
};
const [quote, setQuote] = useState(getRandomQuotes());
const [author, setAuthor] = useState(getRandomQuotes().author);
const [lang, setLang] = useState("En");
const [nextBtn, setNextBtn] = useState("Next");
const handleClickLang = () => {
if (lang === "Fr") {
setLang("En");
setNextBtn("Next");
setQuote(quote.quoteFr);
} else {
setLang("Fr");
setNextBtn("Suivant");
setQuote(quote.quoteEn);
}
};
return (
<div id="quote-box">
<div className="c1">
<div className="lang">
<button onClick={handleClickLang} className="changeLang">
{lang}
</button>
</div>
<div className="card flow">
<p id="text">{quote}</p>
<p id="author">{author}</p>
</div>
</div>
<div className="c2">
<Github id="github" />
<button onClick={handleClickRandomQuote} id="new-quote">
{nextBtn}
</button>
</div>
</div>
);
}
export default App;
and i get this error : Error: Objects are not valid as a React child (found: object with keys {quoteEn, quoteFr, author}).
You are storing in your quote state an object (the one you randomly select from your array of quoteData), not the string for the quote. This is not bad, as you also want to show the author of the random quote you randomly selected.
Also, when you do:
const [quote, setQuote] = useState(getRandomQuotes());
const [author, setAuthor] = useState(getRandomQuotes().author);
You have to take into account that the first execution of getRandomQuotes will return a random quote that doesn't have to coincide with the second call to getRandomQuotes to get the author. This could cause that your data (quote and author) are not in sync.
So, it seems that storing the author separately from the quote is not necessary. Taking all this into account, you could do something like:
function App() {
const getRandomQuotes = () => {
const randNumb = Math.floor(Math.random() * quoteData.length);
return quoteData[randNumb];
};
const [quote, setQuote] = useState(getRandomQuotes());
const [lang, setLang] = useState("En");
const [nextBtn, setNextBtn] = useState("Next");
const handleClickLang = () => {
if (lang === "Fr") {
setLang("En");
setNextBtn("Next");
} else {
setLang("Fr");
setNextBtn("Suivant");
}
};
return (
<div id="quote-box">
<div className="c1">
<div className="lang">
<button onClick={handleClickLang} className="changeLang">
{lang}
</button>
</div>
<div className="card flow">
<p id="text">{quote[`quote${lang}`]}</p>
<p id="author">{quote.author}</p>
</div>
</div>
<div className="c2">
<Github id="github" />
<button onClick={handleClickRandomQuote} id="new-quote">
{nextBtn}
</button>
</div>
</div>
);
}
I'm using PubSub to globalize some states on my React application. I've a "Home" and a "Escolas" Component, "Escolas" is already using PubSub to share his status with a Component called "Filters". Works fine.
But now, my user starts the application on "Home", there, he enter a name on some input and I want to save this value on a topic of PubSub, but when I try to publish, the return is false.
setEscola(collection, e) {
this.setState({ escola: e });
var isPublished = PubSub.publish('escola-filtro', collection);
console.log(isPublished);
}
This is my entire Component:
import React, { Component } from 'react';
import PubSub from 'pubsub-js';
import lupa from '../../img/lupa.png';
import { Link } from 'react-router-dom';
import MenuHome from '../MenuSuperior/MenuHome';
import { listarEscolas } from '../../services/escolas';
import SelectAutocomplete from '../Inputs/SelectAutocomplete';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
escola : '',
escolas : []
}
this.setEscola = this.setEscola.bind(this);
this.testeEstado = this.testeEstado.bind(this);
}
componentDidMount() {
listarEscolas().then(
lista => {
let escolas = [];
lista.results.forEach(function(escola) {
escolas.push({value : escola.codesc, label : escola.nomesc })
});
this.setState({ escolas : escolas });
}
)
}
componentWillMount() {
PubSub.clearAllSubscriptions();
}
buscarEscolas = (e) => {
if (e.target.value.length >= 3) {
let escolas = [];
listarEscolas(e.target.value).then(
lista => {
lista.results.forEach(function(escola) {
escolas.push({value : escola.codesc, label : escola.nomesc });
});
this.setState({ escolas : escolas });
}
)
}
}
setEscola(collection, e) {
// this.setState({ escola: e });
// var isPublished = PubSub.publishSync('escola-filtro', collection);
// console.log(isPublished);
}
testeEstado(event) {
console.log(event.target.value);
var isPublished = PubSub.publishSync('filtro', event.target.value);
console.log(isPublished);
}
render() {
return(
<div>
<MenuHome />
<div className="w-100 mapa-home">
<div className="container d-flex justify-content-center">
<div className="col-lg-5 text-center position-absolute conteudo">
<h2>Aqui você encontra todas as informações sobre sua escola</h2>
<div className="form-group mt-4">
<input type="text" className="form-control form-control-lg" onChange={this.testeEstado} />
<SelectAutocomplete
value={this.state.escola}
collection={this.state.escolas}
className="form-control form-control-lg rounded-pill shadow m-90 d-inline-block"
placeholder="Encontre sua escola pelo nome ou bairro"
onChange={this.setEscola}
onKeyDown={this.buscarEscolas}
/>
<Link className="btn btn-light d-inline-block ml-1" to="/escolas"><img src={lupa} alt="Buscar" /></Link>
</div>
</div>
</div>
</div>
</div>
);
}
}
Try this:
async function testeEstado(event) {
console.log(event.target.value);
var isPublished = await PubSub.publishSync('filtro', event.target.value);
console.log(isPublished);
}
The Async Await model should work for what you are trying to test here. I am uncertain if it will solve the issues if they are more underlying though.