ReactJS - Creating children components by looping through object - object

I have parent and child component. I want the parent to render multiple child components with properties specified in an object. I cannot seem to make the loop in the render function work.
var Inputs = React.createClass({
propTypes: {
type: React.PropTypes.string,
xmltag: React.PropTypes.string,
class: React.PropTypes.string
},
getDefaultProps: function () {
return {
type: ' text'
};
},
render: function() {
return (
<div className={'form-element col-xs-6 col-sm-6 ' + this.props.class}>
<label className="col-xs-12">{this.props.text}</label>
<input className="col-xs-12" type={this.props.type} xmltag={this.props.xmltag}></input>
</div>
);
},
});
//OBJECT that needs to be rendered
var formTags = {
id: ["ID", "List ID", "text"],
title: ["TITLE", "List Title", "text"],
said: ["SAID", "SAID", "number"]
};
var InputList = React.createClass({
//PROBLEM STARTS HERE
render: function() {
for (var key in formTags) {
return (
//Not using everything from formTags
<Inputs type="number" text={key}>
);
};
},
//PROBLEM ENDS HERE
});
ReactDOM.render(<InputList />, document.getElementById('mainForm'));

React component must have only one root element, now you are trying render several elements, add one root element, like in example (you can use any elements <div> <p> etc.)
var InputList = React.createClass({
render: function() {
var inputs = Object.keys(this.props.tags).map(function (key) {
return <Inputs key={ key } type="number" text={ key } />;
});
return <div>
{ inputs }
</div>;
}
});
Example

Related

How to emit data in Vue after api call is finished

I have a windows.addEventListener that triggers an API call. I need to pass a name from the api call to the parent component to display it as a title on a dashboard. How can I emit the data to my parent component
child:
<sankey-input-details v-if="!tableLoading" :cardListData="dataFromApi"/>
async apiData() {
const nodeData = this.dateRange
? await get('nodeData', {
dateStart: this.dateRange[0],
dateEnd: this.dateRange[1],
nodeName: this.nodeName
}) : await get('nodeData', {
nodeName: this.nodeName
});
nodeData[0].name <-------- data to be emitted.
this.tableLoading = false;
parent:
props: {
title: String,
cardListData: Array
},
Here you go, You can emit the event to parent from child by using $emit function.
Vue.component('child', {
props: ['childmsg'],
template: '<p>{{ childmsg }}</p>',
mounted() {
// Just for the demo I am emiting the value directly but you can replace that with your API call code.
const nodeData = [{
name: 'Parent Header'
}];
this.$emit('headername', nodeData[0].name);
}
});
var app = new Vue({
el: '#app',
data: {
headerText: null
},
methods: {
showName(e) {
this.headerText = e;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>{{ headerText }}</h1>
<child childmsg="This is a child component" #headername="showName"></child>
</div>
https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events
The $emit() method is also available on the component instance as this.$emit():
so in your method:
this.$emit('emitName', nodeData[0].name)

Prop being mutated: "isOpen"

I got this error:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Avoid mutating a prop directly
since the value will be overwritten whenever the parent component
re-renders. Instead, use a data or computed property based on the
prop's value. Prop being mutated: "isOpen"
Here is my code
My child component:
<template>
<v-dialog v-model="isOpen" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="$emit('closedialog')">Close</v-btn>
<!-- <v-btn color="primary" text #click="deleteItem">Delete</v-btn> -->
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
// name: 'confirmDelete',
props: {
isOpen: Boolean
// selected: Object
}
};
</script>
Parent Component:
<template>
<div class="container">
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="contracts"
sort-by="createdAt"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>CONTRACTS</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn
color="primary"
dark
class="mb-2"
v-bind="attrs"
v-on="on"
>New Contract</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field
label="Start Contract"
name="name"
prepend-icon="person"
placeholder="YYYY-MM-DD"
type="text"
required
v-model="selectedItem.startDate"
:rules="nameErrors"
#input="$v.selectedItem.startDate.$touch()"
#blur="$v.selectedItem.startDate.$touch()"
#keyup="clearServerErrors('name')"
/>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="selectedItem.duration"
:items="[1, 2, 3, 6, 12]"
label="Duration Contract."
required
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-select
v-model="selectedItem.leave"
:items="[20, 26]"
label="Days off"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn
color="blue darken-1"
text
#click="onSave"
:disabled="!isValid"
>Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">mdi-pencil</v-icon>
<v-icon small #click="showDeleteDialog(item)">mdi-delete</v-icon>
<v-icon middle #click="goToRouteLeaves(item)">play_arrow</v-icon>
</template>
</v-data-table>
<CreateOrEditContract :is-open="isDialogDeleteVisible" #closedialog="close()" />
<!-- <v-dialog v-model="isDialogDeleteVisible" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="isDialogDeleteVisible = false">Close</v-btn>
<v-btn color="primary" text #click="deleteItem">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>-->
</v-app>
</div>
</div>
</template>
<script>
import ContractService from '../services/ContractService';
import UserContractsService from '../services/UserContractsService';
import { validationMixin } from 'vuelidate';
import { required } from 'vuelidate/lib/validators';
import CreateOrEditContract from './CreateOrEditContract';
var moment = require('moment');
export default {
name: 'Admin',
components: {
CreateOrEditContract
},
mixins: [validationMixin],
validations: {
selectedItem: {
startDate: {
required,
isStartDate(value) {
return this.isStartDate(value);
}
}
}
},
data() {
return {
selectedItem: {
startDate: ''
},
serverErrors: {
startDate: ''
},
errorMessage: '',
error: null,
validationError: false,
contracts: [],
dialog: false,
isDialogDeleteVisible: false,
headers: [
{ text: 'Start', value: 'startDate' },
{ text: 'Duration', value: 'duration' },
{ text: 'Leave', value: 'leave' },
{ text: 'Actions', value: 'actions', sortable: false }
],
defaultItem: {
startDate: '',
duration: '',
leave: ''
}
};
},
created() {
this.selectedItem = { ...this.defaultItem };
},
async mounted() {
try {
const { userId } = this.$route.params;
const { data } = await UserContractsService.index(userId);
this.contracts = data;
} catch (error) {
this.errorMessage =
(error.response && error.response.data ? error.response.data : null) ||
error.message ||
error.toString();
}
},
computed: {
formTitle() {
return this.selectedItem.id ? 'Edit Contract' : 'New Contract';
},
nameErrors() {
const errors = [];
if (!this.$v.selectedItem.startDate.$dirty) return errors;
!this.$v.selectedItem.startDate.required && errors.push('Date is required');
!this.$v.selectedItem.startDate.isStartDate && errors.push('Enter valid date');
return errors;
},
isValid() {
return !this.$v.$invalid;
}
},
watch: {
dialog(val) {
val || this.close();
}
},
methods: {
editItem(item) {
this.selectedItem = { ...item };
this.dialog = true;
},
async deleteItem() {
const index = this.contracts.findIndex((contract) => contract.id === this.selectedItemlete.id);
this.contracts.splice(index, 1);
this.isDialogDeleteVisible = false;
await ContractService.delete(this.selectedItemlete.id);
this.selectedItemlete = { ...this.defaultItem };
},
showDeleteDialog(item) {
this.selectedItemlete = item;
this.isDialogDeleteVisible = true; //!this.isDialogDeleteVisible;
// this.$emit("clicked", !this.isDialogDeleteVisible)
},
close() {
this.isDialogDeleteVisible = false;
this.dialog = false;
this.selectedItem = { ...this.defaultItem };
},
async onSave() {
if (this.selectedItem.id) {
const index = this.contracts.findIndex((contract) => contract.id === this.selectedItem.id);
await ContractService.save(this.selectedItem);
this.$set(this.contracts, index, this.selectedItem);
} else {
this.selectedItem.userId = this.$route.params.userId;
const { data } = await ContractService.save(this.selectedItem);
this.contracts.push(data);
}
this.close();
},
goToRouteLeaves(item) {
this.$router.push(`/leaves/${item.id}`);
},
clearServerErrors(type) {
this.serverErrors[type] = [];
},
isStartDate(value) {
return moment(value, 'YYYY-MM-DD', true).isValid();
}
}
};
</script>
<style scoped>
v-btn {
position: absolute;
}
</style>
v-model="isOpen" your child component is trying to change the props isOpen.
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
make change like below:
Parent:
<CreateOrEditContract :is-open.sync="isDialogDeleteVisible" #closedialog="close()" />
Child:
computed: {
open: {
// getter
get: function () {
return this.isOpen
},
// setter
set: function (newValue) {
this.$emit('update:isOpen', newValue)
}
}
}
<v-dialog v-model="open" max-width="500px">
It's because props work from top to bottom pattern. isOpen passed by parent to child, now it's like data flowing from top to bottom. If your child tries to mutate that data, how will parents get informed about that change? The parent will never get informed this way that's why it's a warning to not change the value of prop passed in child. You need to find a way to communicate to parents and parents will update that prop, this way data flow will not break.
Here v-model is two-way binding which means it will set the value of the property which is isOpen prop.
<template>
<v-dialog v-model="isOpen" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="$emit('closedialog')">Close</v-btn>
<!-- <v-btn color="primary" text #click="deleteItem">Delete</v-btn> -->
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
// name: 'confirmDelete',
props: {
isOpen: Boolean
// selected: Object
}
};
</script>
Hint: You can emit event to inform parent about the change and make parent change the value of isOpen. Try computed getter/setter to achieve this

How can I modify this code so that render waits for Promise Items?

I am new to React and I load all the data from my database initially on page load but there is info I need to find in an array and apparently it isn't instant. What do I need to do to make sure the render method only renders the objects when the object promises have resolved?
I haven't tried much... I'm really stuck here.
This seems different than the other problems I've read here because I load a bunch on info in the beginning just fine but I need to call some team information every time a function is called so it isn't as simple as loading it once because the object i need is always different.
This code is the main issue. I also included the full file below:
I did some modification to the code in a edit: I realized that I just need to call the opponent team because I have the player team already.
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
Full file:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Async from 'react-promise'
import { withFirebase } from '../Firebase';
// import * as ROUTES from '../../constants/routes';
import { Container, Image, Spinner, Col, Row, Card, Accordion, Button } from 'react-bootstrap'
class PlayerGameList extends Component {
constructor(props) {
super(props);
this.state = {
loadingTeams: false,
loadingSchedule: false,
teams: [],
schedule: []
};
}
componentDidMount() {
this.setState({
loadingTeams: true,
loadingSchedule: true,
});
this.unsubscribe = this.props.firebase
.teams()
.where('players', 'array-contains', '-LXkkB7GNvYrU4UkUMle')
.onSnapshot(snapshot => {
let teams = [];
snapshot.forEach(doc =>
teams.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
teams,
loadingTeams: false,
});
});
this.unsubscribe2 = this.props.firebase
.schedule()
.onSnapshot(snap => {
let schedule = [];
snap.forEach(doc =>
schedule.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
schedule,
loadingSchedule: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsubscribe2();
}
render() {
const { teams, schedule, loadingTeams, loadingSchedule } = this.state;
return (
<div>
<h2>Games</h2>
{loadingTeams && loadingSchedule && <div colSpan="12"><Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner></div>}
{/* CONTENT */}
<Container fluid>
<Row>
{getTeams({ teams, schedule })}
</Row>
</Container>
</div >
);
}
}
function getTeams({ teams, schedule }) {
if (!teams) {
return null;
}
if (!teams.length) {
return null;
} else {
return teams.map(team => getGames({ team, schedule }))
}
}
function getGames({ team, schedule }) {
schedule.sort((a, b) => (a.time > b.time) ? -1 : 1)
if (!schedule) {
return null;
}
if (!schedule.length) {
return null;
} else {
return schedule.map(game => guts({ team, game }));
}
}
function guts({ team, game }) {
const image = {
height: '25px',
width: '25px'
}
if (team.id === game.team_1) {
var redTeam = team;
// set blueTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_2}')
} else {
var blueTeam = team;
// set redTeam based on game.team_1
// firebase.teams().doc('teams/{game.team_1}')
}
if (game.team_1 === team.id || game.team_2 === team.id) {
var time = new Date(game.time.seconds * 1000);
var dateFormat = require('dateformat');
var finalTime = dateFormat(time, 'ddd mmm dd, h:MM tt')
return (
<Col lg='4' md='6' sm='12' key={game.uid} style={{ marginBottom: '15px' }}>
<Card>
<Card.Body>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'red' }}></div>
</Row>
<Row>
<Image src={team.logo} style={image} roundedCircle />
<p>{team.name}</p>
<div style={{ height: '25px', width: '25px', backgroundColor: 'blue' }}></div>
</Row>
<Row>
<div>
{finalTime}
</div>
</Row>
</Card.Body>
<Accordion>
<Card style={{ margin: '0', padding: '0' }}>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Show Match IDs
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>{game.match_id}</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Card>
</Col>
);
}
}
export default withFirebase(PlayerGameList);
The items all load blank then a few seconds later all the console logs come through with the array objects. When I tell it to await the program just throws an error.

How to paginate a list of posts belonging to a specific category in GatsbyJS

I have developed a blog with Gatsby JS and I managed to add categories to each markdown file so that I can create pages by querying a specific category and list all the posts related to that category.
Now, I'm trying to add pagination to avoid an infinite list of posts inside each category page.
I have been following the official guide here: https://www.gatsbyjs.org/docs/adding-pagination/
And this is the code I came up with:
gatsby-node.js
const path = require('path')
const _ = require("lodash")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
const articleTemplate = path.resolve(`src/templates/article.js`)
const categoryTemplate = path.resolve("src/templates/category.js")
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 2000
) {
edges {
node {
html
id
frontmatter {
path
title
categories
}
}
}
}
}
`).then(result => {
if (result.errors) {
reject(result.errors)
}
const articles = result.data.allMarkdownRemark.edges
const articlesPerPage = 6
const numPages = Math.ceil(articles.length / articlesPerPage)
//Creating a page for each article
articles.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: articleTemplate,
//context: {}, // additional data can be passed via context
})
})
// Categories pages:
let categories = []
// Iterate through each article, putting all found categories into `categories`
_.each(articles, edge => {
if (_.get(edge, "node.frontmatter.categories")) {
categories = categories.concat(edge.node.frontmatter.categories)
}
})
Array.from({ length: numPages }).forEach((category, _, i) => {
createPage({
path: i === 0 ? `/${_.kebabCase(category)}/` : `/${_.kebabCase(category)}/${i + 1}`,
component: categoryTemplate,
context: {
limit: articlesPerPage,
skip: i * articlesPerPage,
category,
},
})
})
})
)
})
/templates/categories.js
import React from "react"
import PropTypes from "prop-types"
import Layout from '../layouts/layout'
import ArticleCard from '../components/articles/articlecard'
// Components
import { Link, graphql } from "gatsby"
const _ = require("lodash")
const Categories = ({ pageContext, data }) => {
const { category } = pageContext
const { edges } = data.allMarkdownRemark
return (
<Layout>
<section class="hero is-info is-medium has-text-centered">
<div class="hero-body">
<div class="container">
<h1 class="title is-top">
{category}
</h1>
</div>
</div>
</section>
<div class="section">
<div class="container">
<div class="columns is-multiline">
{edges.map(({ node }) => {
const { path, title, date } = node.frontmatter
return (
<div class="column is-half">
<div class="card">
<div class="card-header">
<p class="card-header-content">{date}</p>
</div>
<div class="card-content">
<Link to={_.kebabCase(category)}><span class="tag is-success has-padding">{category}</span></Link>
<Link to={path}>
<h2 class="title is-4">{title}</h2>
</Link>
</div>
<div class="card-footer">
<div class="card-footer-item"><Link to={path}><div class="button is-success is-inverted is-fullwidth">Read</div></Link></div>
<div class="card-footer-item"><Link to={path}><div class="button is-info is-inverted is-fullwidth">Share on Linkedin</div></Link></div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</Layout>
)
}
Categories.propTypes = {
pageContext: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Categories
export const pageQuery = graphql`
query($skip: Int!, $limit: Int!, $category: String) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { categories: { in: [$category] } } }
limit: $limit
skip: $skip
) {
totalCount
edges {
node {
frontmatter {
title
path
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
This does not work and it is now throwing the error: error gatsby-node.js returned an error, TypeError: _.kebabCase is not a function
However kebabCase was used smoothly before modifying the query to add pagination, so I don't think the problem is actually there.
Does anyone have any clue?
Thank you!
You are declaring the variable "underscore" twice:
1- from the lodash library
2- from the forEach function:
Array.from({ length: numPages }).forEach((category, _, i)
just change the the second variable to another arbitrary name like this:
Array.from({ length: numPages }).forEach((category, otherName, i)

React and Socket.io

I'm trying to create a simple app using ReactJS and Socket.io
In my component I want to be able to communicate with the server, but the problem is that I don't know how to do io.connect()
1.Do I need to explicitly specify the IP address like io.connect("http://myHost:7000") or is it enough to say : io.connect() ? As we can see in this piece of code :
https://github.com/DanialK/ReactJS-Realtime-Chat/blob/master/client/app.jsx
2.I do more or less the same as this code , but I receive error when I do npm start as io is undefined. I think , io is provided globally by including the socket.io script. How can I solve this problem ?
'use strict';
var React = require('react');
var socket = io.connect();
var chatWindow = React.createClass({
displayName: 'chatWindow',
propTypes: {},
getDefaultProps: function() {
return ({
messages: 0
});
},
componentDidMount: function() {
socket = this.props.io.connect();
socket.on('value', this._messageRecieve);
},
_messageRecieve: function(messages) {
this.setState({
messages: messages
});
},
getInitialState: function() {
return ({
messages: 0
});
},
_handleSend: function(){
var newValue = parseInt(this.refs.messageBox.value) + this.props.messages;
this.setState({
messages: newValue
});
socket.emit('clientMessage', { message: newValue});
},
render: function() {
var window =
<div>
<div>{this.props.messages}</div>
<input type="text" id="messageBox" refs="messageBox"></input>
<input type="button" onClick={this._handleSend} value="send" id="send"/>
</div>;
return (window);
}
});
module.exports = chatWindow;
This is the code :
https://github.com/arian-hosseinzadeh/simple-user-list
Answers:
1) No, you don't need to specify the IP, you can even use / and it will go through the default HTTP 80 port, anyway, you can find more examples on the socket.io site.
2) Require io too, remember to add socket.io-client to your package:
var React = require('react'),
io = require('socket.io-client');
Anyway, if you want to include the client script that socket.io server provides as a static file, then remember to add it into your HTML using a <script/> tag, that way you'll have io on the global scope avoiding the require part, but well, I prefer to require it.
NOW, WHAT ABOUT...
Trying my lib: https://www.npmjs.com/package/react-socket
It will handle the socket connection on mount and disconnection on unmount (the same goes for socket event listeners), give it a try and let me know.
Here you have an example:
http://coma.github.io/react-socket/
var App = React.createClass({
getInitialState: function() {
return {
tweets: []
};
},
onTweet: function(tweet) {
var tweets = this
.state
.tweets
.slice();
tweet.url = 'https://twitter.com/' + tweet.user + '/status/' + tweet.id;
tweet.at = new Date(tweet.at);
tweet.avatar = {
backgroundImage: 'url(' + tweet.img + ')'
};
tweets.unshift(tweet);
this.setState({
tweets: tweets
});
},
renderTweet: function (tweet) {
return (
<li key={tweet.id}>
<a href={tweet.url} target="_blank">
<div className="user">
<div className="avatar" style={ tweet.avatar }/>
<div className="name">{ tweet.user }</div>
</div>
<div className="text">{ tweet.text }</div>
</a>
</li>
);
},
render: function () {
return (
<div>
<ReactSocket.Socket url="http://tweets.socket.io"/>
<ReactSocket.Event name="tweet" callback={ this.onTweet }/>
<ul className="tweets">{ this.state.tweets.map(this.renderTweet) }</ul>
</div>
);
}
});
React.render(<App/>, document.body);

Resources