Related
I'm trying to use an array of dictionaries in python as arguement to a custom dash component and use it as array of objects
in python :
audioList_py = [
{
"name": "random",
"singer": 'waveGAN\'s music',
"cover":
'link_1.jpg',
"musicSrc":
'link_1.mp3',
},
{
"name": "random",
"singer": 'waveGAN\'s music',
"cover":
'link_2.jpg',
"musicSrc":
'link_2.mp3',
},
... etc
]
in Javascript:
audioList1_js = [
{
name: "random",
singer: 'waveGAN\'s music',
cover:'link_1.jpg',
musicSrc: 'link_1.mp3',
},
{
name: "random",
singer: 'waveGAN\'s music',
cover: 'link_2.jpg',
musicSrc: 'link_2.mp3',
},
... etc
]
Here is snippet of javascript code of the dash custom component:
export default class MusicComponent extends Component {
render() {
const {id, audioLists} = this.props;
return (
<div>
<h1>{id}</h1>
<ReactJkMusicPlayer audioLists={audio_list}/>,
</div>
);
}
}
MusicComponent.defaultProps = {};
MusicComponent.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
audios: PropTypes.array,
id: PropTypes.string,
};
And using the generated component in python:
app = dash.Dash(__name__)
app.layout = html.Div([
music_component.MusicComponent(audios=audioList_py),
html.Div(id='output'),
... etc
])
But I got :
TypeError: The `music_component.MusicComponent` component (version 0.0.1) received an unexpected keyword argument: `audios`Allowed arguments: id
What I am doing wrong ?
Any help or advice will be appreciated, Thanks a lot.
Make sure you run npm run build after you make a change to your custom React component. With those proptypes you shouldn't get that error. If I remove the audios proptype I can reproduce that error.
Besides that you pass a value to the audios property:
music_component.MusicComponent(audios=audioList_py)
but you try to retrieve audioLists from props:
const {id, audioLists} = this.props;
Change this to:
const {id, audios} = this.props;
Demo
export default class MusicComponent extends Component {
render() {
const {id, audios} = this.props;
return (
<div>
<h1>{id}</h1>
<ReactJkMusicPlayer audioLists={audios} />
</div>
);
}
}
MusicComponent.defaultProps = {};
MusicComponent.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,
audios: PropTypes.array,
};
Issue fixed, I should run : npm run build:backends to generate the Python, R and Julia class files for the components, but instead I was executing npm run build:js and this command just generate the JavaScript bundle (which didn't know about the new props).
And set the audios property in the component to be like so:
MusicComponent.defaultProps = {audios: audioList1};
MusicComponent.propTypes = {
id: PropTypes.string,
audios: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)).isRequired
};
I am new to React hooks and trying to learn it. I have a simple get api which is served in heroku server. I have following two components ContactCards and ContactCard in which I have provided the api in ContactCards.
Here I have thumbnai and title as static data where as data1 and data2 are data coming from the api.
My ContactCard
import React, { Component } from "react";
interface IContactCardProps {
thumbnail?: string;
title?: string;
data1?: string;
data2?: string;
}
interface IContactCardState {}
class ContactCard extends Component<IContactCardProps, IContactCardState> {
render() {
return (
<div className="contact-card">
<div className="container">
<div className="thumbnail">
<img src={this.props.thumbnail} alt={this.props.title} />
</div>
<div className="title">{this.props.title}</div>
<div className="data">{this.props.data1}</div>
<div className="data">{this.props.data2}</div>
</div>
</div>
);
}
}
export default ContactCard;
My ContactCards
import ContactCard from "./ContactCard";
import axios from "axios";
function ContactCards() {
const[contacts, setContacts] = useState([])
useEffect(() => {
axios.get('https://texas-crm1.herokuapp.com/api/contactinfo')
.then (res =>{
console.log(res.data[0])
let data = res.data[0];
setContacts ({
contacts: [
{
thumbnail: clock,
title: "Opening Times",
data1: data.open_hour,
data2: data.close_hour,
},
{
thumbnail: telephone,
title: "Phone",
data1: data.phone_1,
data2: data.phone_2,
},
{
thumbnail: location,
title: "Location",
data1: data.street_name,
data2: data.city + ", " + data.state + ", " + data.country,
},
],
});
})
.catch(err =>{
console.log(err)
})
})
return (
<div>
</div>
)
}
Here, I got red underline from contacts in setContacts to all the way down to the closing parenthesis before catch function.
And the error says the following:
Argument of type '{ contacts: { thumbnail: string; title: string; data1: any; data2: any; }[]; }' is not assignable to parameter of type 'SetStateAction<never[]>'.
{ contacts: []} is an object, your initial state is using [] and that's an array. IOW: Your trying to use setState on something that's currently an array, into something that will become an object that contains an array called contacts.
What I would do is create an interface for you useState, based on your IContactCardProps
Something like ->
const[contacts, setContacts] =
useState<{contacts:IContactCardProps[]}>
({contacts: []});
Your default setState now is an object with the correct shape for your setContacts. The advantage here too is that your setContacts is now type checked, eg. You couldn't accidentally put t1tle: "Phone",.
Instead of giving it an array of any, initialize a typed array and initialize your useState with that:
const emptyContacts: IContactCardProps[] = [];
const [contacts, setContacts] = useState(emptyContacts);
So, the following implementation works just fine to read JSON data and turn it into rendered components - until I try to add the children. Then, it spits out an error.
function:
const catalogRenderer = (config) => {
if (typeof KeysToComponentMap[config.component] !== "undefined") {
return React.createElement(
KeysToComponentMap[config.component],
{
key: config.key,
title: config.title
},
{
config.children && config.children.map(c => catalogRenderer(c))
}
);
}
}
error:
app.js:134 Uncaught Error: Module build failed (from ./node_modules/babel-loader/lib/index.js)
"...Scripts/CatalogRenderer.js: Unexpected token, expected "," (25:14)"
console:
},
24 | {
> 25 | config.children && config.children.map(c => catalogRenderer(c))
| ^
26 | }
27 | );
28 | }
I'm using react as part of an electron application, it's a long story about all the moving parts, but everything else so far has worked just fine. In the editor, if I move to the preceding { from that mysteriously disliked . on line 25, it's highlighting the period as if this should somehow close the bracket.
Is there something I'm not understanding about the syntax here? The same thing happens if I attempt to just map and render the children like so:
{
config.children.map(c => catalogRenderer(c))
}
I've tried enclosing the whole statement in brackets, curly braces, parentheses--no matter what I do, babel seems to expect a comma, but giving it a comma obviously doesn't help. Any idea what I'm doing wrong?
eta: This is the JSON object I'm attempting to render from:
const catConfig = {
catalog: [
{
component: 'pen',
title: `B.C. Palmer`,
key: `B.C.PalmerPen`,
children: `A child string`
},
{
component: 'content',
key: `B.C.PalmerWorldList`,
children: [
{
component: 'world',
title: `Rismere`,
key: `RismereWorld`
},
{
component: 'content',
key: `RismereSeries`,
children: [
{
component: 'series',
title: `The Eidolon War`,
key: `TheEidolonWarSeries`
},
{
component: 'content',
key: `TheEidolonWarBooks`,
children: [
{
component: 'book',
title: `Magic's Heart`,
key: `MagicsHeartBook`
},
{
component: 'book',
title: `Magic's Fury`,
key: `MagicsFuryBook`
},
{
component: 'book',
title: `Magic's Grace`,
key: `MagicsGraceBook`
}
]
}
]
}
]
},
{
component: 'pen',
title: `Simon Strange`,
key: `SimonStrangePen`
}
]
}
This JSON will be generated via a database call, and written each time the database is updated, and update the state of the 'catalog' component.
So, for example, the second object in the catalog array above is a container which, when the first 'pen' component is clicked, becomes visible and shows a list of 'world' components (in this case, just the one.) However, the function only successfully renders any 'parent' components--if I take out the curly braces at lines 24 and 26, it simply skips them but doesn't error.
The components are composed of button elements and a div (content). The buttons will likely become Link element when I get this working, but the original version was written in vanilla javascript, I haven't implemented routing with the catalog yet. So, the pen component for example:
import React from 'react'
export default penButton => {
return(
<button className="catalogItem pen">
<img src="src/icons/catPenName.png" className="catalogIcon"/>
<p className="contentLabel">{penButton.title}</p>
</button>
)
}
Is a top level component, and gets rendered just fine. It's next sibling (and the next sibling of any button except a book) is content:
import React from 'react'
export default contentList => {
return(
<div className="contentList">
</div>
)
}
contentList is just a div with the contentList class, which handles visibility and animation. Should I have a place for the "children" key in JSON to populate the children of content?
When you want to render multipile children elemen'ts into your react component, you need to pass each child as a seperate parameter.
See this answer as an example:
how to render multiple children without JSX
so your solution should be to use spread syntax.
here is an example:
const catalogRenderer = (config) => {
if (typeof KeysToComponentMap[config.component] !== "undefined") {
let childs = [];
if (config.children) {
childs = config.children.map(c => catalogRenderer(c));
}
return React.createElement(
KeysToComponentMap[config.component],
{
key: config.key,
title: config.title
},
...childs
);
}
}
Well, that was simple and a little silly. I updated the content component to:
import React from 'react'
export default contentList => {
return(
<div className="contentList">
{contentList.children} <---- added
</div>
)
}
Content didn't have a place to put children. Obviously.
My component is supposed to retrieve the data for courses when the component mounts. The problem that I have is that whether I use the course Id or the course title as the key, I get the following error:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I have looked through the react docs, here on Stack Overflow, and tried different ways to get it to work. The only way I can get it to partially work is by adding an index as a parameter for map. When I use this method, I run into another problem and that is, it stops after the first iteration, even though there are 10 items. How can I fix this?
Here is my code:
CoursesPage.js
import React from 'react';
import { connect } from 'react-redux';
import * as courseActions from '../../redux/actions/courseActions';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
class CoursesPage extends React.Component {
componentDidMount() {
this.props.actions.loadCourses().catch(error => {
alert("Loading courses failed" + error);
});
}
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
</>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
courses: state.courses
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
My mock data:
const courses = [
{
id: 1,
title: "Securing React Apps with Auth0",
slug: "react-auth0-authentication-security",
authorId: 1,
category: "JavaScript"
},
{
id: 2,
title: "React: The Big Picture",
slug: "react-big-picture",
authorId: 1,
category: "JavaScript"
},
{
id: 3,
title: "Creating Reusable React Components",
slug: "react-creating-reusable-components",
authorId: 1,
category: "JavaScript"
},
{
id: 4,
title: "Building a JavaScript Development Environment",
slug: "javascript-development-environment",
authorId: 1,
category: "JavaScript"
},
{
id: 5,
title: "Building Applications with React and Redux",
slug: "react-redux-react-router-es6",
authorId: 1,
category: "JavaScript"
},
{
id: 6,
title: "Building Applications in React and Flux",
slug: "react-flux-building-applications",
authorId: 1,
category: "JavaScript"
},
{
id: 7,
title: "Clean Code: Writing Code for Humans",
slug: "writing-clean-code-humans",
authorId: 1,
category: "Software Practices"
},
{
id: 8,
title: "Architecture Applications for the Real World",
slug: "architecting-applications-dotnet",
authorId: 1,
category: "Software Architecture"
},
{
id: 9,
title: "Becoming an Outlier: Reprogramming the Developer Mind",
slug: "career-reboot-for-developer-mind",
authorId: 1,
category: "Career"
},
{
id: 10,
title: "Web Component Fundamentals",
slug: "web-components-shadow-dom",
authorId: 1,
category: "HTML5"
}
];
const authors = [
{ id: 1, name: "Cory House" },
{ id: 2, name: "Scott Allen" },
{ id: 3, name: "Dan Wahlin" }
];
const newCourse = {
id: null,
title: "",
authorId: null,
category: ""
};
module.exports = {
newCourse,
courses,
authors
};
Edit:
I am using Redux Thunk.
Here is my actionType.js file:
export const CREATE_COURSE = "CREATE_COURSE";
export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";
Here is my CourseActions.js file:
import * as types from './actionTypes';
import * as courseApi from "../../api/courseApi";
export function createCourse(course) {
return { type: types.CREATE_COURSE, course };
}
export function loadCourseSuccess(courses) {
return { type: types.LOAD_COURSES_SUCCESS, courses };
}
export function loadCourses() {
return function (dispatch) {
return courseApi.getCourses().then(courses => {
dispatch(loadCourseSuccess(courses));
}).catch(error => {
throw error;
})
}
}
Here is my courseReducer.js file:
import * as types from '../actions/actionTypes';
export default function courseReducer(state = [], action) {
switch (action.type) {
case types.CREATE_COURSE:
return [...state, { ...action.course }];
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
default:
return state;
}
}
Any help would be appreciated.
Thanks.
P.S. I know that you should use Id for key. But the way it has to be done for now is using the title of the course as the key.
With your edits, I think we can more effectively help you. For future, it would be beneficial to post an example of your code not working on https://codesandbox.io/
Also to help yourself out when you debug, isolate the react component from the use of redux. This will allow you to ensure your react component renders when given data, then focus on getting redux to provide you the data your react component has.
You can do this by first defining mockdata inside your react component, then moving that mock data to your reducer, then finally replacing the mock data with the live api call.
On to the code:
You have two issues: the first is that you want to index into the array courses but instead due to a typo, you are actually using the property accessor into the object course
key={course[index].title}
As your question states you must use the title as the key simply change the div to be:
<div key={course.title}>{course.title}</div> and your code should work as expected.
Once you have addressed that, then re-enable loading data from your API call using Redux, and you can address the issues with Redux.
Looking at your reducer, you have a clear bug, and depending on your use case, a potential bug:
case types.LOAD_COURSES_SUCCESS:
return [...state, { ...action.courses }];
action.courses is an array, and the code is creating a new array that contains all the elements of the previous array state and adding a new object, which contains the contents of a destructured array.courses array.
Which that does is effectively append a single object to your array, and the object is comprised of elements from your courses array. The item index becomes the key, and the item itself is the value.
You can visualize it here: https://codesandbox.io/s/divine-pond-nr3j8
Instead you want
return [...state, ...action.courses];
The second, potential bug, is that you are actually appending the results of the courses api. Which for subsequent calls to load courses, you will duplicate the data. That may be what you want, but I am going to assume that is not what you want.
So instead, your LOAD_COURSES_SUCCESS case should be rewritten to simply be:
return [...action.courses];
Since each course has a unique ID field, the problem could be solved by using the id as a key
render() {
return (
<>
<h2>Courses</h2>
{this.props.courses.map(course => (
<div key={course.id}>{course.title}</div>
))}
</>
);
}
The issue is here:
{this.props.courses.map((course, index) => (
<div key={course[index].title}>{course[index].title}</div>
))}
//course in itself is data
Solution:
{this.props.courses.map((course, index) => (
<div key={`${course.title}-${index}`}>{course.title}</div>
))}
Better way would always to take care unique id for key
Here's the code snippet for render method.
Give it a try.
*Update = use course title as key
render() {
const courseList = this.props.courses.map(course => (
<div key={course.title}></div>
));
return (
<>
<h2>Courses</h2>
<div>{courseList}</div>
</>
);
}
There are many ways you can pass the key when it is iterating...
var courses = this.state.courses.map(function(course, index) {
return(
<div key={index}>
<div key={course.title} id={course.title}>
<h2 key={"header"+course.title}>{course.title}</h2>
</div>
</div>
)
});
But you should try to pass a unique id rather than a string.
I have a <form> in vue. I send that form to server, get a JSON response, print it to console. It works fine.
However I need to take that JSON response and display it on another page. For instance, I have two .vue files: GetAnimal.vue that has the form and retrieves the animal data from an API and a DisplayAnimal.vue that displays animal's data. I need to direct the response animal data from GetAnimal.vue to DisplayAnimal.vue.
GetAnimal.vue:
<template>
<form v-on:submit.prevent="getAnimal()">
<textarea v-model = "animal"
name = "animal" type="animal" id = "animal"
placeholder="Enter your animal here">
</textarea>
<button class = "custom-button dark-button"
type="submit">Get animal</button>
</form>
</template>
<script>
import axios from 'axios';
export default {
name: 'App',
data: function() {
return {
info: '',
animal: ''
}
},
methods: {
getAnimal: function() {
axios
.get('http://localhost:8088/animalsapi?animal=' + this.animal)
.then(response => (this.info = response.data));
console.log(this.info);
}
}
}
</script>
response:
retrieves a JSON with animal data, say like this:
{
"fur-color": "yellow",
"population": 51000,
"isExtinct": false,
"isDomesticated": true
}
and I now want to give that JSON to a DisplayAnimal.vue at /viewanimal endpoint:
DisplayAnimal.vue:
<template>
<div>
<p>Animal name: {{animal}}}</p>
<p>Fur color: {{furColor}}</p>
<p>Population: {{population}}</p>
<p>Is extinct: {{isExtinct}}</p>
<p>Is domesticated: {{isDomesticated}}</p>
</div>
</template>
How would I do that? I know I can redirect via this.$router.push({ path });, but I've only used it for navigation, while here JSON response needs to be passed. Is this even a correct / good practice way of approaching this?
EDIT:
I tried this:
in GetAnimal.vue I added this data:
data: function() {
return {
animal: {
name: 'Cat',
furColor: 'red',
population: '10000',
isExtinct: false,
isDomesticated: true
}
and in DisplayAnimal.vue this:
<script>
export default {
props: {
animal: {
name: {
type: String
},
furColor: {
type: String
},
population: String,
isExtinct: String,
isDomesticated: String
}
}
}
</script>
and in GetAnimal.vue I added this:
methods: {
animals: function() {
alert("animals");
this.$router.push({name: 'viewanimal',
query: {animal: JSON.stringify(this.animal)}});
},
to try to display that test animal using the display component. However it just didn't work - I get an empty page.
Using Vuex, you can solve this easily
Working example on netlify
https://m-animalfarm.netlify.app/
code on github
https://github.com/manojkmishra/animalfarm
GetAnimal.vue ( I have disabled axios call for testing and hardcoded info)
<template>
<form v-on:submit.prevent="getAnimal()">
<textarea v-model = "animal" name = "animal" type="animal" id = "animal"
placeholder="Enter your animal here">
</textarea>
<button class = "custom-button dark-button"
type="submit">Get animal</button>
</form>
</template>
<script>
import axios from 'axios';
export default
{
name: 'App',
data: function() { return { info: '', animal: '' } },
methods: {
getAnimal: function() {
// axios
// .get('http://localhost:8088/animalsapi?animal=' + this.animal)
// .then(response => (this.info = response.data),
this.info={"fur-color": "yellow","population": 51000,"isExtinct":
false,"isDomesticated": true },
this.$store.dispatch('storeAnimals', this.info)
//);
}
}
}
</script>
DisplayAnimal.vue
<template>
<div>
<p>Animal name: {{stateAnimal.animal}}</p>
<p>Fur color: {{stateAnimal.furColor}}</p>
<p>Population: {{stateAnimal.population}}</p>
<p>Is extinct: {{stateAnimal.isExtinct}}</p>
<p>Is domesticated: {{stateAnimal.isDomesticated}}</p>
</div>
</template>
<script>
import {mapState, mapGetters} from 'vuex';
export default {
computed:{ ...mapState({ stateAnimal:state => state.modulename.stateAnimal }),
},
}
</script>
modulename.js ( store module)
export default
{
state: {stateAnimal:null, },
getters:{ },
mutations:
{ ['STORE_ANIMALS'] (state, payload)
{ state.stateAnimal = payload;
console.log('state=',state)
},
},
actions:
{ storeAnimals: ({commit}, data) =>
{ console.log('storeanim-data-',data);
commit( 'STORE_ANIMALS', data );
},
}
}
Index.js (for vuex store), you can disable persistedstate as its for saving state if page is refreshed
import Vue from 'vue'
import Vuex from 'vuex'
import modulename from './modules/modulename'
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex)
export default new Vuex.Store({
plugins: [createPersistedState({ storage: sessionStorage })],
state: { },
mutations: { },
actions: { },
modules: { modulename }
})
State is available/shared for all the components
well first of all create a second folder call it services and create service.js for you axios call- good practice and cleaner code overall.
second use vuex. this kind of data is best used with vuex.
As far as I understand GetAnimal.vue is the parent component and you wish to display it in the child component DisplayAnimal.vue.
If so and you wish to see if this works just use props.
you can also send that same information or any other information for the child back to the parent using an $emit().
STRONGLY recommended to use vuex in order to manage the state
Vue.component('product',{
props:{
message:{
type:String,
required:true,
default:'Hi.'
}
},
template:`<div>{{message}}</div>`,
data(){...}
})
//html in the other component you axios call is in this component //<product meesage="hello"></product>
I would pass the animal name/id as a route param to the display page and have that component responsible for fetching and displaying the relevant animal data. This avoids the situation where a user could visit the display page directly via the URL and see an incomplete page.
In situations where you want to share local state between pages, as others have pointed out you'd probably want to use Vuex.
EDIT:
I'm adding some code to my answer as requested by the OP.
Routes:
const routes = [
{ path: "/", component: SearchAnimals },
{ path: "/viewanimal/:name", component: DisplayAnimal, name: "displayAnimal" }
];
DisplayAnimal.vue:
<template>
<div>
<p>Animal name: {{animal.name}}</p>
<p>Fur color: {{animal.furColor}}</p>
<p>Population: {{animal.population}}</p>
<p>Is extinct: {{animal.isExtinct}}</p>
<p>Is domesticated: {{animal.isDomesticated}}</p>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "DisplayAnimal",
data: () => ({
animal: {}
}),
methods: {
fetchAnimal(name) {
axios
.get(`http://localhost:8088/animalsapi?animal=${name}`)
.then(response => {
this.animal = response.data;
});
}
},
created() {
this.fetchAnimal(this.$route.params.name);
}
};
</script>
SearchAnimals.vue:
<template>
<form v-on:submit.prevent="onSubmit">
<textarea
v-model="animal"
name="animal"
type="animal"
id="animal"
placeholder="Enter your animal here"
></textarea>
<button type="submit">Get animal</button>
</form>
</template>
<script>
export default {
name: "SearchAnimal",
data: () => ({
animal: ""
}),
methods: {
onSubmit() {
this.$router.push({
name: "displayAnimal",
params: { name: this.animal }
});
}
}
};
</script>
Obviously this is a bare-bones example, so doesn't contain any error handling etc., but it should get you up and running.