I have data folder that contains events.ts:
export const EventsData: Event[] = [
{
name: 'School-Uniform-Distribution',
images: ['/community/conferences/react-foo.png', "/community/conferences/react-foo.png"],
},
{
name: 'College-Uniform',
images: ['/community/conferences/react-foo.png', "/community/conferences/react-foo.png"],
},
];
type Event is:
export type Event = {
name: string;
images: string[];
};
I have the getStaticPath and getStaticProps methods in pages/our-contribution/[pid].tsx :
export const getStaticProps: GetStaticProps<Props> = async (context) => {
const event = EventsData;
console.log(event, "event")
return {
props: { event: event },
};
};
export async function getStaticPaths() {
// Get the paths we want to pre-render based on posts
const paths = EventsData.map(event => ({
params: {pid: event.name},
}));
console.log(paths, "paths")
// We'll pre-render only these paths at build time.
return {paths, fallback: false}
}
I get this error:
Can you help me, please ?
Update:
This is the error trace for one route:
pages/our-contribution/[pid].tsx:
import { useRouter } from 'next/router';
import { GetStaticProps } from 'next';
import { Event } from 'types/event';
import {EventsData} from 'data/events';
type Props = {
event: Event[];
};
const Event = ({event} : Props) => {
const router = useRouter()
const { pid } = router.query
return <p>Event: {event}</p>
}
export const getStaticProps: GetStaticProps<Props> = async (context) => {
const event = EventsData;
console.log(event, "event")
return {
props: { event: event },
};
};
export async function getStaticPaths() {
// Get the paths we want to pre-render based on posts
const paths = EventsData.map(event => ({
params: {pid: event.name},
}));
console.log(paths, "paths")
// We'll pre-render only these paths at build time.
return {paths, fallback: false}
}
export default Event
I think there are a couple of errors in the code.
One error that block your build is
const Event = ({event} : Props) => {
const router = useRouter()
const { pid } = router.query
return <p>Event: {event}</p>
}
You can't directly print an object or array in tsx, you should convert the object first into string if you are trying to debug it. Something like:
return <p>Event: {event.toString()}</p>
Then a i notice something strange in your variables name event props looks like a single event but instead you give an array of events i don't know if it is correct but maybe it should be like this:
type Props = {
event: Event;
};
or it should be named:
type Props = {
events: Event[];
};
Related
I have several providers / contexts in a React app that do the same, that is, CRUD operations calling a Nestjs app:
export const CompaniesProvider = ({children}: {children: any}) => {
const [companies, setCompanies] = useState([])
const fetchCompany = async () => {
// etc.
try {
// etc.
setCompanies(responseData)
} catch (error) {}
}
const updateCompany = async () => {
// etc.
try {
// etc.
} catch (error) {}
}
// same for delete, findOne etc..
return (
<CompaniesContext.Provider value={{
companies,
saveSCompany,
}}>
{children}
</CompaniesContext.Provider>
)
}
export const useCompanies = () => useContext(CompaniesContext)
Another provider, for instance, the Technology model would look exactly the same, it just changes the api url:
export const TechnologiesProvider = ({children}: {children: any}) => {
const [technologies, setTechnologies] = useState([])
const fetchTechnology = async () => {
// etc.
try {
// etc.
setTechnologies(responseData)
} catch (error) {}
}
const updateTechnology = async () => {
// etc.
try {
// etc.
} catch (error) {}
}
// same for delete, findOne etc..
return (
<TechnologiesContext.Provider value={{
technologies,
savesTechnology,
}}>
{children}
</TechnologiesContext.Provider>
)
}
export const useTechnologies = () => useContext(TechnologiesContext)
What is the best way to refactor? I would like to have an abstract class that implements all the methods and the different model providers inherit the methods, and the abstract class just needs the api url in the constructor..
But React prefers function components so that we can use hooks like useState.
Should I change function components to class components to be able to refactor? But then I lose the hooks capabilities and it's not the react way nowadays.
Another idea would be to inject the abstract class into the function components, and the providers only call for the methods.
Any ideas?
One way to achieve it is to create a factory function that gets a url (and other parameters if needed) and returns a provider & consumer
This is an example for such function:
export const contextFactory = (url: string) => {
const Context = React.createContext([]); // you can also get the default value from the fn parameters
const Provider = ({ children }: { children: any }) => {
const [data, setData] = useState([]);
const fetch = async () => {
// here you can use the url to fetch the data
try {
// etc.
setData(responseData);
} catch (error) {}
};
const update = async () => {
// etc.
try {
// etc.
} catch (error) {}
};
// same for delete, findOne etc..
return (
<Context.Provider
value={{
data,
save
}}
>
{children}
</Context.Provider>
);
};
const hook = () => useContext(Context)
return [Provider, hook]
};
And this is how you can create new providers & consumers
const [CompaniesProvider, useCompanies] = contextFactory('http://...')
const [TechnologiesProvider, useTechnologies] = contextFactory('http://...')
I ended up creating a class representing the CRUD operations:
export class CrudModel {
private api = externalUrls.api;
constructor(private modelUrl: string) {}
async fetchRecords() {
const url = `${this.api}/${this.modelUrl}`
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-type': 'application/json'
},
})
return await response.json()
} catch (error) {}
}
// removeRecord
// updateRecord
// saveRecord
}
Then for every provider I reduced the code, since I just call an instance of the CrudModel, which has the method implementations.
type Technology = {
id: number;
name: string;
}
type Context = {
technologies: Technology[];
saveTechnology: any;
removeTechnology: any;
updateTechnology: any;
}
const TechnologiesContext = createContext<Context>({
technologies: [],
saveTechnology: null,
removeTechnology: null,
updateTechnology: null,
})
export const TechnologiesProvider = ({children}: {children: any}) => {
const [technologies, setTechnologies] = useState([])
const router = useRouter()
const crudModel = useMemo(() => {
return new CrudModel('technologies')
}, [])
const saveTechnology = async (createForm: any): Promise<void> => {
await crudModel.saveRecord(createForm)
router.reload()
}
// fetchTechnologies
// removeTechnology
// updateTechnology
useEffect(() => {
async function fetchData() {
const fetchedTechnologies = await crudModel.fetchRecords()
setTechnologies(fetchedTechnologies)
}
fetchData()
}, [crudModel])
return (
<TechnologiesContext.Provider value={{
technologies,
saveTechnology,
removeTechnology ,
updateTechnology,
}}>
{children}
</TechnologiesContext.Provider>
)
}
This way I can have types for every file, and it's easy to debug / maintain. Having just one factory function like previous answer it feels cumbersome to follow data flow. The downside is that there is some repetition of code among the provider files. Not sure if I can refactor further my answer
When I fetch data from firebase firestore using getStaticProps, it works perfectly but when I try implementing the logic of getting the details of each single item using getStaticPaths, I fail and get a 404 page. This is how my [id].js code looks like currently.
import React from 'react'
import { db } from '#/Firebase';
import {collection, getDoc} from "firebase/firestore";
const reference = collection(db, "abantu");
export const getStaticPaths= async () => {
const umuntu = await getDoc(reference);
const paths = umuntu.docs.map(doc => {
return {
params: { id: doc.id }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await getDoc(reference) + id;
return {
props: {
umuntu: data
}
}
}
function Details({umuntu}) {
return (
<div>
<h1>{umuntu.ibizo}</h1>
</div>
)
}
export default Details
I dont quite get where my logic is going wrong but where could I be going wrong?.
For finding the right page props for each of the paths that you generate from the database in the getStaticPaths function, you should be able to find each of the pages information based on the id field you are getting from each path, see it here:
export const getStaticProps = async (context) => {
const id = context.params.id;
const umuntu = await getDoc(reference);
const data = umuntu.docs.find((pageData) => pageData.id === id); // this will find the right page based on the id passed via page path
return {
props: {
data
},
};
};
function Details({ data }) {
return (
<div>
<h1>{data.ibizo}</h1>
</div>
);
}
export default Details;
I am trying to test the async fetch() function of my Vue Component. This method uses some of the props to build a query to use with our Apollo GQL instance. I'm having a hard time finding examples using a similar configuration that I am using.
Ultimately, I'd love to test the full query (building it and the return data) but if it is difficult to test the full return data, then I can settle with just testing the building process of the variables and hopefully the schema of the return data.
// ProductList.spec.js
import { shallowMount } from '#vue/test-utils'
import ProductListing from '~/components/blocks/ProductListing.vue'
import PRODUCT_LISTING_BLOCK_QUERY from '~/queries/productListingBlock.gql'
import '#testing-library/jest-dom'
const directives = {
interpolation: () => {}
}
const mocks = {
$md: {
render: value => value
}
}
describe('ProductListing', () => {
test('fetch from apollo', () => {
const testVariables = {
maxItems: 10,
matchSkus: 'ms239,mk332,as484',
matchTags: 'tag1,tag2,tag3',
matchCategories: '111,222,333,444'
}
const wrapper = shallowMount(ProductListing, {
propsData: testVariables,
stubs: ['ProductTileList'],
directives,
mocks
})
const client = this.$apollo.provider.clients.defaultClient // get an error on this line saying $apollo is undefined
const products = await client
.query({
PRODUCT_LISTING_BLOCK_QUERY,
testVariables,
context: {
clientName: 'powerchord'
}
})
.then(r => r.data.allProducts)
// const promise = wrapper.vm.fetch() // this doesn't seem to work/call my function
})
})
// ProductList.vue
<template lang="pug">
.mb-8(:id='elementId')
ProductTileList(:products="products")
</template>
<script lang="ts">
import Vue from 'vue'
import BaseBlockMixin from './BaseBlockMixin'
import query from '~/queries/productListingBlock.gql'
import { IObjectKeys } from '~/types/common-types'
import mixins from '~/utils/typed-mixins'
const Block = Vue.extend({
props: {
matchCategories: {
type: String,
default: ''
},
matchSkus: {
type: String,
default: ''
},
matchTags: {
type: String,
default: ''
},
maxItems: {
type: Number,
default: 20
}
},
data () {
return {
products: []
}
},
async fetch () {
const client = this.$apollo.provider.clients.defaultClient
const variables: IObjectKeys = {
limit: this.maxItems
}
if (this.matchSkus.length) {
variables.skus = this.matchSkus.split(',')
}
if (this.matchTags.length) {
variables.tags = this.matchTags.split(',')
}
if (this.matchCategories.length) {
variables.categories = this.matchCategories.split(',')
.map(c => parseInt(c))
.filter(c => !isNaN(c))
}
this.products = await client
.query({
query,
variables,
context: {
clientName: 'some-company'
}
})
.then((r: any) => r.data.allProducts)
}
})
export default mixins(Block).extend(BaseBlockMixin)
</script>
There is such code in the VUEX repository:
export const state = () => ({
z: 'sdfjkhskldjfhjskjdhfksjdhf',
});
export const mutations = {
init_data_for_firmenistorie2 (state, uploadDbFirmenistorieData){
state.z = uploadDbFirmenistorieData;
},
};
async nuxtServerInit ({commit}) {
console.log('111');
commit('init_data_for_firmenistorie2', 123)
}
}
My question is:
How should I call to nuxtServerInit in such a way that I can use it to rewrite the value of state z?
P.S. Right now my code is not working.
If your store/index.js has an action nuxtServerInit, then Nuxt will invoke it.
So your code ends up looking like
export const state = () => ({
z: 'sdfjkhskldjfhjskjdhfksjdhf',
});
export const mutations = {
init_data_for_firmenistorie2(state, uploadDbFirmenistorieData) {
state.z = uploadDbFirmenistorieData;
},
};
export const actions = {
nuxtServerInit({ commit }) {
console.log('111');
commit('init_data_for_firmenistorie2', 123);
},
};
Well you create a actions object, and then you put your nuxtServerInit in there:
export const actions = {
nuxtServerInit(vuexContext, context){
vuexContext.commit('init_data_for_firmenistorie2', 123);
}
}
With context you can additional have access to for example params, routes, redirect etc.
The docs: https://nuxtjs.org/api/context
I cannot figure out why this is not working:
-spec.js
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
},
store = fakeStore(state),
container = <HomePageContainer store={store} />;
const homePageContainer = shallow(container),
homePage = homePageContainer.find(HomePage);
expect(homePage.props.company.name).to.equal(state.company.name)
});
const fakeStore = state => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
HomePageContainer.js
import React from 'react';
import { connect } from 'react-redux'
import HomePage from '../../client/components/HomePage';
export const mapStateToProps = state => {
company: state.company
}
export { HomePage }
export default connect(mapStateToProps)(HomePage);
HomePage.js
import React, { Component, PropTypes } from 'react';
export default class HomePage extends Component {
render(){
return (
<div className='homepage'>
{/*{this.props.company.name}*/}
</div>
)
}
}
I'm getting this type error because props.company is undefined so for some reason it's not persisting state to :
TypeError: Cannot read property 'name' of undefined
That error is relating to the assert when it's trying to read expect(homePage.props.company.name)
I notice that when putting a breakpoint inside mapStateToProps, that it's not picking up the state object from the store still for some reason:
I know you can pass just the store via props...and if there's nothing in the context, connect() will be able to find it via the store prop. For example in another test suite, this passes just fine:
it('shallow render container and dive into child', () => {
const container = shallow(<ExampleContainer store={fakeStore({})}/>);
expect(container.find(Example).dive().text()).to.equal('Andy');
});
Problem
At this line you pass the store as a prop into <HomePageContainer>:
// ...
container = <HomePageContainer store={store} />;
// ...
But connect() needs the store via context.
And you must wrap the object you want to return in mapStateToProps in parenthese.
Solution
You can use shallow() to make the store available as a context property.
it.only('passes props to children', () => {
const state = {
company: {
name: 'Company A'
}
};
const store = fakeStore(state);
const homePageContainer = shallow(
<HomePageContainer />,
// Make the store available via context
{ context: { store } }
);
const homePage = homePageContainer.find(HomePage);
expect(homePage.props().company.name).to.equal(state.company.name)
});
Updated mapStateToProps:
export const mapStateToProps = state => ({
company: state.company
});
Found out the problem was really my helper.
This was passing a object with property "state" in it. Not what I want. That would have meant that mapStateToProps would have had to reference the props with state.state.somePropName
const fakeStore = (state) => {
return {
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => { return { state };
},
};
};
changed it to this, and now mapStateToProps is working fine, it's able to access it with the props as the root level of the object literal, the object I passed in from my test:
const fakeStore = (state) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});