SurveyJS and NextJS - Issue when setting new state of variable - reactjs

Version of NextJS: 12.0.8
Version of SurveyJS: 1.9.5
Testing Browser: Chrome, FF
Testing the app (vercel):
You can test the app here: https://jem-surveyjs-nextjs.vercel.app/
Description of the issue:
I combine SurveyJS with NextJS to generate a multi-page survey. The code for the survey is added into a component.
A custom variable can be manipulated using useState.
Every time the variable is set to a different value the survey reloads and jumps back to the first page instead of staying on the current page.
You can clearly see this effect in the dev-tools console.
To replicate the issue simply do the following:
Create a Nextjs app
Add the survey-react module (npm i survey-react)
add the following content to assets/json/survey-json.js
export const surveyJSON = {
"pages": [
{
"name": "page1",
"elements": [
{
"type": "radiogroup",
"name": "Speech Test",
"hasComment": true,
"commentText": "add comment",
"choices": [
"item1",
"item2",
"item3"
]
}
]
},
{
"name": "page2",
"elements": [
{
"type": "radiogroup",
"name": "Try again",
"hasComment": true,
"commentText": "add comment",
"choices": [
"item4",
"item5",
"item6"
]
}
]
}
]
};
Add the following content to components/Survey.js
import {useState, useEffect} from 'react'
import * as SurveyJs from "survey-react";
import "survey-react/survey.css";
import {surveyJSON} from "../assets/js/survey-json";
// Survey Component
const Survey = (props) => {
const [custParam, setCustParam] = useState(false);
// Avoid rehydration conflict
// https://nextjs.org/docs/messages/react-hydration-error
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
/**
* Handle Custom Function
*/
const handleClick = () => {
// Example 3:
setCustParam(!custParam);
console.log("HANDLE CLICK - CUSTOM PARAM: ", custParam)
};
/**
* afterRenderQuestionHandler
*/
const afterRenderQuestionHandler = function (survey, options) {
// Example 2:
//setCustParam(!custParam);
console.log("AFTER RENDER QUESTION - CUSTOM PARAM: " , custParam)
const tarea = document.querySelector('.sv_q_other');
if ( tarea ) {
tarea.onclick = function(){
handleClick();
}
}
}
let mySurvey = new SurveyJs.Model(surveyJSON);
mySurvey.showCompletedPage = true;
// Example 1:
//setCustParam(true);
console.log("BEFORE RENDER SURVEY - CUSTOM PARAM: " , custParam)
return (
<SurveyJs.Survey
model={mySurvey}
onAfterRenderQuestion={afterRenderQuestionHandler}
/>
);
}
export default Survey;
Finally add this to your pages/index.js
import styles from '../styles/Home.module.css'
import Survey from '../components/Survey'
export default function Home() {
return (
<div>
<Survey />
</div>
)
}
As you can see I have added 3 different examples of how the issue can be created in the components/Survey.js. Example 3 is the active one right now, but you can un-comment Example 2 or Example 1 to see a different effect of the issue.
How can I avoid this reloading and jump to the first page?
Any help that can point me in the right direction would be appreciated
UPDATE:
Many thanks #juliomalves!!
I have added a useState for my Survey Object
const [mySurvey, setMySurvey] = useState({});
and inside the already existing useEffect I've added the following:
setMySurvey( new SurveyJs.Model(surveyJSON) );
I finally just had to remove this line to make it all work:
let mySurvey = new SurveyJs.Model(surveyJSON);

Related

how to read a multipage API using react using Axios

so im trying to use this api that even if you call just the element/people he returns only ten people instead of the 82 total i want , so i have to slowly go through them all and keep adding the values on a list, i wanted to have a list with all the character names and one with all the other results,so i did this (im using SWAPI star wars api btw)
import React, { useEffect, useState } from "react";
import api from "./components/services/api";
export default function App() {
const [listPeople, setList] = useState();
const [page,setPage]=useState(1)
const [names,setNames]=useState([])
const [full,setFull]=useState([])
const [finished,setFinished] =useState(false);
useEffect(() => {
api
.get(`people/?page=${page}`)
.then((response) => setList(response.data))
.catch((err) => {
console.error("an error occured: " + err);
});
},[page]);
function generateList() {
if(listPeople.next != null) {
var i=0
while(i <9) {
setPage(page +1)
setNames(names.concat(listPeople.results.map((res) => (res.name))))
setFull(full.concat(listPeople.results))
i++
}
}
else {
if(!finished) {
setNames(names.concat(listPeople.results.map((res) => (res.name))))
setFull(full.concat(listPeople.results))
setFinished(true)
}
}
console.log(names)
console.log(full)
}
return (
<>
<button onClick={generateList}>click here!</button>
<ul></ul>
</>
);
}
as you can see i need to press a button everytime i want to get the rest of the elements in the api(first 10 buttons then 20.. until 82)
what i wanted was to click the button once and have the whole list(of only the names and of all the results) already in an array/object
but if i try to do that using a while loop the hoops it doesnt work,i ve tried using mapp but cant think of a way to apply it in this situation and the few that i did got me the same effect, and there is no numeric atribute on the api that shows the number of pages, just one that says next with the next page url, like so
ex:https://swapi.dev/api/people
"count": 82,
"next": "https://swapi.dev/api/people/?page=2",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.dev/api/planets/1/",
"films": [
"https://swapi.dev/api/films/1/",
"https://swapi.dev/api/films/2/",
"https://swapi.dev/api/films/3/",
"https://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
"https://swapi.dev/api/vehicles/14/",
"https://swapi.dev/api/vehicles/30/"
],
"starships": [
"https://swapi.dev/api/starships/12/",
"https://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "https://swapi.dev/api/people/1/"
}, ... until the 10th person
again i just want to press the button and have the entire list in my console.log, and the '/people' adress doesnt return everything.
im still a beginner at react so every help is appreciated, thanks :)

Dynamic import and component rendering by string name

I have a data set for some simple actions. Each action has a corresponding color and Material-UI icon. There are about 1,100 available icons. The data looks like this:
{ "items":
[{ "action": "Run",
"icon": "DirectionsRunIcon",
"color":"Red"},
{ "action": "Jump",
"icon": "ArrowUpwardIcon",
"color":"Blue"},
{ "action": "Walk",
"icon": "DirectionsWalkIcon",
"color":"Green"},
]}
Each of the icons requires importing a separate library like so:
import ArrowUpwardIcon from '#material-ui/icons/ArrowUpward'
I could be using any number of icons from #material-ui/icons/ and they're obviously stored as a string in the data. So the question is, how can I conditionally render the icons and only load what I need?
Currently I have a function that just does a huge switch, which won't work long term.
function getIcon(action) {
switch (action) {
case "Run":
return <DirectionsRunIcon />;
case "Jump":
return <ArrowUpwardIcon />;
...
Thanks!
Using something like #loadable/component, you could try to load dynamicly the icon with
import loadable from '#loadable/component';
const Icon = ({name, ...rest}) => {
const ImportedIcon = loadable(() =>
import(`#material-ui/icons/${name}`),
);
return <ImportedIcon {...rest} />;
};
Since you have already have the reference name of the Icon in your datset, you may use the function below:
import { find } from 'lodash';
const getIcon = (action) => {
const requiredObj = find(items, { action }) || {};
return requiredObj.icon;
};
Now, you can use the resulting name to do a dynamic import.

How can I display three random exercises from an array every time a button is pressed using React?

I'm trying to display a list of 3 random exercises every time you press a button and change those 3 exercises on each button press
it chooses a random workout correctly in the console, but I'm not sure how to make it display and update correctly using React.
how can I make it so it displays a random 3 exercises every time the button is pressed. I've seen ways of doing it using only JS but am curious how to do this with React.
import React, { Component } from "react";
class Workout extends Component {
render() {
const upperExercises = [
"test1",
"test2",
"test3"
]
const lowerExercises = [
"test4",
"test5",
"test6"
]
const coreExercises = [
"test7",
"test8",
"test9"
]
function getAll() {
let randomUpper = upperExercises[Math.floor(Math.random() * upperExercises.length)];
console.log(randomUpper)
let randomLower = lowerExercises[Math.floor(Math.random() * lowerExercises.length)];
console.log(randomLower)
let randomCore = coreExercises[Math.floor(Math.random() * coreExercises.length)];
console.log(randomCore)
}
return (
<section>
<button onClick={() => { getAll() }}>TEST</button>
<p>DISPLAY OUTPUT</p>
</section>
)
}
}
export default Workout
You can try something like this. You can randomly get the values from your arrays and then setState of the values so you can access them easily in your <p/> tags.
I changed your getAll function to an arrow function to avoid having to bind to this . It's more simpler for beginners to use Es6 arrow functions. Let me know if this worked . Good Luck!!
import React, { Component } from "react"
const upperExercises = [
"test1",
"test2",
"test3"
]
const lowerExercises = [
"test4",
"test5",
"test6"
]
const coreExercises = [
"test7",
"test8",
"test9"
]
class Workout extends Component {
constructor(props) {
super(props)
//initialise state
this.state = {
lower ='',
upper ='',
core ='',
}
}
//Changed your function to arrow function to avoid binding and its
// a simplier way of doing it
getAll = () => {
const randomUpper = Math.floor(Math.random() * upperExercises.length);
const upper = upperExercises[randomUpper]
//Get the random value and set the state to that value
this.setState({
upper: upper
})
const randomLower = Math.floor(Math.random() * lowerExercises.length);
const lower = lowerExercises[randomLower]
this.setState({
lower: lower
})
const randomCore = Math.floor(Math.random() * randomCore.length);
const core = coreExercises[randomCore]
this.setState({
core: core
})
}
render() {
return (
<section>
<button onClick={this.getAll}>TEST</button>
<p>Upper {this.state.upper}</p>
<p>Lower {this.state.lower}</p>
<p>Core {this.state.core}</p>
</section>
)
}
}
export default Workout

Linking or Routing dynamicaly with Next.js

I am trying to find the best way to link to a details page from a list of objects that are mapped. The list fetches items from an API and works fine. The problem is that I cant pass the id to my details page and get this error when I click on the objects.
:3000/_next/static/development/pages/_app.js?ts=1592696161086:1299 GET http://localhost:3000/flowers/[object%20Object] 404 (Not Found)
and in the url http://localhost:3000/flowers/[object%20Object]
This is what I have in my /pages/flowers.js
import React, { useState, useEffect } from 'react'
import Head from 'next/head'
import Layout from '../components/layout'
import Link from 'next/link'
import utilStyles from '../styles/utils.module.css'
export default function Flowers() {
const LISTURL = 'https://flowers-mock-data.firebaseio.com/flowers.json'
const TESTURL = 'https://flowers-mock-data.firebaseio.com/flowers/9.json'
const [items, setItems] = useState([])
useEffect(() => {
fetch(LISTURL)
.then((res) => res.json())
.then((json) => setItems(json))
}, [] )
return (
<Layout>
<Head>
<title>🌸</title>
</Head>
<section className={utilStyles.centerSection}>
<button>Shade loving plants</button>
<button>Sun loving plants</button>
</section>
{items.map((item) => (
<ul key={item._id.oid}>
<li className={utilStyles.listItem}>
<Link href='/flowers/[flowerid]' as={`/flowers/${item._id}`}>
<a className={utilStyles.list}>
<h2 className={utilStyles.headingTop}>{item.common_name}</h2>
<img className={utilStyles.imgList} src={`${item.cover_image}`} alt={item.common_name} />
</a>
</Link>
</li>
</ul>
))
}
</Layout>
)
}
This is pages/[flowerid].js
import React, { useEffect, useState } from 'react'
import Head from 'next/head'
import Router from 'next/router'
import axios from 'axios'
import Layout from '../components/layout'
import utilStyles from '../styles/utils.module.css'
const FlowerDetail = () => {
const [flower, setFlower] = useState(null)
useEffect(() => {
const fetchData = async () => {
const { flowerId } = Router.query
try {
const { data } = await axios.get(
`https://flowers-mock-data.firebaseio.com/flowers/${flowerId}.json`
)
console.log(`blomma`, data)
setFlower(data)
} catch (error) {
console.log(`Can not find id`, error)
}
}
fetchData()
}, [])
if (!flower) return <div>Loading...</div>
console.log('no flowers to see')
return (
<Layout>
<Head>
<title>🌸</title>
</Head>
<div>
<p>Blooming season {flower.blooming_season}</p>
<img className={utilStyles.imgList} src={`${flower.cover_image}`} alt={flower.common_name} />
<p>Common name {flower.common_name}</p>
<h5>{flower.notes}</h5>
</div>
</Layout>
)
}
export default FlowerDetail;
The problems holding you back...
The _id coming from your API is an object which contains an oid property:
"_id": {
"oid": "5e57880c9fa50a095f475c18"
},
Therefore you'll want to use the items._id.oid property as a link source. As is, you're passing the id object to the link source, which is why your links are invalid: http://localhost:3000/flowers/[object%20Object]
Unfortunately, that's not the only problem. The other problem is that the current data structure doesn't contain any reference to the URL parameter id (where 1.json is supposed to reference oid: 5e579cdfe99bdd0ca1cf64a3, 2.json references oid: 5e579dfcb664120cd14b4331, and so on), so you'll need to reconfigure your API data to include a property with this URL id within each data document (id: 1, id: 2, ...etc) and then use that id as your link source OR you'll have to reconfigure your API to utilize this oid property as a URL parameter to match against (instead of 1.json it'll use the oid as a URL parameter to match against and be 5e579cdfe99bdd0ca1cf64a3.json).
For example, this API endpoint (which comes from http://jsonplaceholder.typicode.com) contains an id property that is used as a URL parameter to retrieve the specific JSON file by its id (notice how both the URL and the JSON data contain an id of 9).
On a side note, if you're using Chrome, you can install this extension to prettify/format JSON, so it looks like this.
OID doesn't match the requested API endpoint
For reference, here's how your response is currently structured when you query this endpoint (notice that the 9 in https://flowers-mock-data.firebaseio.com/flowers/9.json isn't referenced/used anywhere within the JSON data, so using the oid (https://flowers-mock-data.firebaseio.com/flowers/5e57a0e1e5f2470d789335c2.json) as a parameter won't be valid):
{
"__v": {
"numberInt": "0"
},
"_id": {
"oid": "5e57a0e1e5f2470d789335c2"
},
"blooming_season": "Late Spring",
"common_name": "Ornamental onion",
"cover_image": "https://images.unsplash.com/photo-1565685225009-fc85d9109c80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80",
"depth": {
"numberInt": "3"
},
"height": [
{
"numberInt": "6"
},
{
"numberInt": "60"
}
],
"latin_name": "Allium",
"notes": "Usually pest-free; a great cut flower",
"soil": [
"well drained",
"moist",
"fertile"
],
"spacing": {
"numberInt": "12"
},
"sun": true
}
Example fetch data repo
I've put together an example with some comments that incorporates the API endpoint (jsonplaceholder) mentioned in the paragraph above, as well as implementing some of nextjs's data fetching methods (excluded getStaticPaths for simplicity; however, technically, it can replace getServerSideProps in the dynamic pages/users/[id].js page since the data will never be changed/updated during runtime):
fetch data example repo

cannot access value passed from context api in firestore fetch data function

so I am trying to fetch some data from my firestore through the following bit of code
import React, { useContext, useState, useEffect } from 'react';
import { CocktailContext } from '../../context/CocktailContext';
import fire, { db } from '../../Config/firebase';
import RecipeCard from '../RecipeCard/RecipeCard';
import NavBar from '../NavBar/NavBar';
export default function SavedItemPage() {
const [ content, setContent ] = useState(null);
const { uid } = useContext(CocktailContext);
useEffect(() => {
listenForMessages(uid)
}, []);
const listenForMessages = (id) => {
db.collection('users').doc(id).onSnapshot(function(doc) {
const allMessages = [];
allMessages.push(doc.data());
setContent(allMessages);
console.log(content);
});
};
return (
<div>
<NavBar />
</div>
);
}
what should basically happen here is that the context API which looks like below, takes the uid and feeds it into listenForMessages() which then does the job of fetching the content
{
"name": "State",
"value": null,
"subHooks": []
},
{
"name": "Context",
"value": {
"recipes": "[{…}, {…}, {…}, {…}, {…}, {…}]",
"searchTerm": "Æ’ searchTerm() {}",
"updateUid": "Æ’ updateUid() {}",
"uid": "MfLXrE5czaYK7fYiTFuqFv9SZV02"
},
"subHooks": []
},
{
"name": "Effect",
"value": "Æ’ () {}",
"subHooks": []
}
]
trouble is that when the page loads i am met with this errors- FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: ""
so the trouble is that listenForMessages() is not able to access the uid value in the context API.
Can anyone tell me how to solve this issue?
I don't know how Firebase works, but the useContext maybe is incorrect ?
usually you use it this way :
[state, dispatch] = useContext(CocktailContext)
Which will give you the whole state. In the state you provided there is 3 objects in your state so i guess you will access the uid by doing something like:
useEffect(() => listenForMessages(state[1].value.uid));
I see there is a ' ] ' closing your provided state but nothing at the beginning, just adapt in case i miss a node level.

Resources