How do I render an array (strings) of components in React? - javascript

I'm trying to render the array of text when I load the page:
'values': ['hello', 'world']
but it's giving me an error. In my formatoc.js , I have:
var props = {
'name' : 'form',
'timer' : 1500,
'callback' : function(id, validity, value) {console.log(id, validity, value);},
'values': ['hello', 'world'],
'newParent' : new FormatOC.Parent({
"one": {"__array__":"unique", "__type__":"string","__minimum__":1,"__maximum__":200,"__component__":"Input"}
})
}
React.render(React.createElement(ParentComponent, props), document.getElementById('react-component'));
This is what I have in my Parent.jsx file:
define(['react', 'r_foc_node'], function(React, NodeComponent) {
var ParentComponent = React.createClass({
getInitialState: function() {
if(!this.props.newParent) {
throw "ParentComponent requires newParent property";
}
return {
"values": {}
}
},
recursive : function(level) {
that = this;
console.log(level);
for (keys in level) {
console.log("This is a key: ", keys);
if ((typeof level[keys]) === "object") {
if (level[keys].constructor.name === "Parent") {
console.log('I am a parent');
level = level[keys].details();
//console.log(level[keys].get([key_list[0]]));
this.recursive(level);
//console.log(level[keys].keys());
} else {
console.log('I am a node');
//console.log(level[keys]);
};
};
}
},
childChange: function(name, valid, value) {
this.state.values[name] = value;
this.setState(this.state);
console.log(name,value);
// Call parent callback
this.props.callback(
this.props.name,
this.props.newParent.valid(this.state.values),
this.state.values
);
},
render: function() {
var that = this;
var level = this.props;
return (
<div id = "form">
{level.newParent.keys().map(function(k) {
return (
<div>
{(level.newParent.get(k).constructor.name === "Parent") ?
<ParentComponent
name={k}
key={k}
timer={that.props.timer}
callback={that.childChange}
values={that.props.values[k]}
newParent={that.props.newParent.get(k)}
/>
:
<NodeComponent
name={k}
key={that.props.name + '.' + k}
timer={that.props.timer}
callback={that.childChange}
values={that.props.values[k]}
newNode={that.props.newParent.get(k)}
/>
}
</div>
)
})
}
</div>
)
}
I'm not sure if the error is because of the way I'm trying to access the array values? But I can't seem to get the words hello and world to be rendered.

You are using: level.newParent.keys().map(function(k) {...
this should be: level.values.map(function(k) {...

Related

Update nested array using setState()

I want to setState() to edit parameter value which is in parameterData Array.
using setState method. If we can do this without any third part library like Immutabilty helper or lodash that would be great!
The Given state is
const[state,setState]= useState ([{
"id": 0,
"targets": {
"ageGender": {
"key":value
},
"parameterData": [
{
"id": 1,
"parameter": "Low",
"expression": "<",
"val": "10",
"indicator": "Select"
},
]
}
}])
I have tried this solution
where
idx = state index where object is situated index = index of
parameterData array to be changed event = event of change
const handleChangeTextParameter = (event, index, idx) => {
const { name, value } = event.target;
setState((prev) => {
const newState = prev.map((obj, index1) => {
let fil;
if (index1 === idx) {
fil = obj.targets.parameterData.map((data, indexOne) => {
if (indexOne === index) {
return { ...data, parameter: value };
}
return data;
});
const p = obj.targets.parameterData.map(
(obj) => fil.find((o) => o.id === obj.id) || obj
);
return { ...obj, targets: { ...obj.targets, parameterData: p } };
}
});
return newState;
});
};
Disclaimer: The state variables you defined are called state and setState, but in your handleChangeTextParameter you are using setTargets so I assume they are the same
I think the following should work:
setTargets((prev) => {
const newTargets = prev.map((object, objectIndex) => {
if (objectIndex !== idx) return object;
return {
...object,
targets: {
...object.targets,
parameterData: object.target.parameterData.map((data, dataIndex) => {
if (dataIndex !== index) return data;
return { ...data, parameter: value };
}),
},
};
});
return newTargets;
});

Filter tasks doesn't work - Todo list with MVC pattern using es6 classes

I'm trying to write a todo list project with pure JS using MVC pattern. It's my first project with MVC and I have a real problem that I can't solve.
I have three buttons, each button gets a value as filter value (in Model class) for complete, active and all task. default value for filter is 0 that refers to all button.
when complete button is active, new todo adds to page, but that page is only for complete todo and new todo must add to all pages while complete page is still active.
I write some methods for handle it, but they doesn't work and I can't understand where is the problem.
how can I solve it?
this is my code:
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS) {
this.onTodoListChanged(todoS);
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input
id="taskInput"
placeholder="New task..."
autocomplete="off"
/>
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
The issue is that in filterTodo, you just filter the tasks but not _commit the change.
So
Store the filter in the model
Move the filter to _commit (so it will keep filter for any other actions such as add todo)
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS = this.todoS) {
this.onTodoListChanged(todoS.filter(todo => {
if (this.filter === 0) return true;
return this.filter === 1 ? !todo.complete : todo.complete;
}));
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.filter = filter;
this._commit();
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
console.log(todoS);
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input id="taskInput" placeholder="New task..." autocomplete="off" />
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
https://stackblitz.com/edit/js-1u6dxi
I suspect the biggest issues in the returning of values. For example this:
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
This code does not return anything, it only calls this.model.filterTodo. In the corresponding method filterTodo, you are creating a new array by using this.todoS.filter but you do not return it anywhere:
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
You could do something like what you did with toggleTodo and the map function here:
filterTodo(filter) {
this.todoS = this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
...but that would only work once, as setting the filter would remove the other todos from your database.
From my understanding of the code (without trying it out), I'd probably set the filter only and whenever _commit gets called, pass the filtered version of the todos based on the selected filter
constructor() {
...
this.possibleFilters = {
0: () => true,
1: todo => !todo.completed,
2: todo => todo.completed
};
}
filterTodo(filter) {
this.filter = filter;
}
_commit(todoS) {
const selectedFilter = this.possibleFilters[this.filter];
this.onTodoListChanged(todoS.filter(selectedFilter));
}

How to validate child component using vue ant design?

I have this component DynamicSelect (child compoenent) and I'm using it in an another component (parent) but when I try to validate my childe component, it deliver always the value as null so the validation is always false
DynamicSelect Component:
<template>
<a-select
:showSearch="true"
:placeholder=placeholder
:value="selectedValue"
#search="searchRegex($event)"
#change="$emit('changed-item', setChangedItem($event))"
#select="$emit('selected-item', setSelectedItem($event))"
:filterOption="filterOption"
>
<a-select-option
v-for="(item,idx) in dropdownData"
:value="idx"
:key="idx"
>{{item.text}}</a-select-option>
</a-select>
</template>
<script>
export default {
name: "DynamicSelect",
data(){
return{
dropdownData: [],
copyDropdownData:[],
selectedValue: undefined
}
},
props:{
//Input data collection
dataSrc:Array,
//Placeholder for input field
placeholder: String,
//if true the dropdown will be automatically cleared after element selected
resetAfterSelect: false,
// List of id to filter the dropdown list
lookFor:Array,
// Data to display in the dropdown if not set, lookFor list will be displayed
displayedValues: Array,
//Default Value
defaultValues:String,
},
beforeMount(){
this.checkDefaultVariable();
},
watch:{
dataSrc:function(newVar,oldVar) { // watch it
this.checkDefaultVariable()
}
},
methods:{
//Search for search term in the data collection 'lookFor' elements to set the dropdown list
async searchRegex(term){
if(term.length>2) {
let searchTerm = new RegExp(term.toUpperCase());
this.dropdownData = await this.filterData(searchTerm);
this.copyDropdownData = JSON.parse(JSON.stringify(this.dropdownData));
}
else
{
this.dropdownData = [];
this.copyDropdownData = [];
}
},
filterData(searchTerm){
return this.dataSrc.filter(x => {
let filtered= [];
for (let i=0; i<this.lookFor.length;i++){
if(x[this.lookFor[i]])
{
if(searchTerm.test(x[this.lookFor[i]].toUpperCase()))
{
let text = '';
if(this.displayedValues !== undefined)
{
for (let k=0; k<this.displayedValues.length;k++)
{
text += x[this.displayedValues[k]];
if(k < this.displayedValues.length-1)
text += ', '
}
}
else {
for (let k=0; k<this.lookFor.length;k++)
{
text += x[this.lookFor[k]];
if(k < this.lookFor.length-1)
text += ', '
}
}
x.text = text;
filtered.push(x);
}
}
}
return filtered.length>0
});
},
// just a logger
logger(event){
console.log(event);
},
async checkDefaultVariable(){
if (this.defaultValues !== '' && this.defaultValues !== undefined && this.dataSrc.length>0 ){
// console.log('DATA',this.dataSrc);
await this.searchRegex(this.defaultValues);
let selected = await this.setSelectedItem(0);
this.$emit('selected-item', selected)
}
},
// return the selected Item as an Object
setSelectedItem(id){
// console.log('ON SELECT');
let temp = JSON.parse(JSON.stringify(this.dropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.dropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
setChangedItem(id){
let temp = JSON.parse(JSON.stringify(this.copyDropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.copyDropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
// search in the dropdown list
filterOption(input, option) {
let searchTerm = new RegExp(input.toUpperCase());
if(searchTerm.test(this.dropdownData[option.key].text.toUpperCase()))
return true;
else {
for(let i=0;i<this.lookFor.length;i++){
if(searchTerm.test(this.dropdownData[option.key][this.lookFor[i]].toUpperCase()))
return true;
else if(i >= this.lookFor.length)
return false;
}
}
}
}
}
</script>
parent component:
<template>
<dynamic-select
:dataSrc="users"
placeholder="Lastname, Firstname"
#selected-item="onSelectUser($event)"
#changed-item="onSelectUser($event)"
:lookFor="['lastname','firstname']"
v-decorator="['contact', {valuePropName:'selectedValue',
rules: [{ required: true,
validator: userExists,
message: 'Error'}]}]"
>
</dynamic-select>
</template>
<script>
.
.
.
methods: {
userExists(rule, value, callback) {
console.log('VALUE', value); //always undefined
console.log('RULES',rule);
console.log('CALLBACK',callback)
return value !== null && value !== undefined && value.length > 2;
},
onSelectUser(user) {
console.log("user: " , user); // set with the selected value
}
},
.
.
.
</script>
I expect that the child component returns the selected value like when emitting an event, I also tried with models but it hasn't helped
thanks :)
You can easily communicate between components
Vue.config.debug = true;
// Parent
let App = new Vue({
el: "#the-parent",
data(){
return{ msg: "Nothing.." };
},
methods:{
receivedFromChild(request){
this.msg = request;
}
},
// Children
components: {
'children': {
template: `
<div><button #click="request">Send to parent!</button>` + `<input type="text" v-model="text"></div>`,
props: [ 'childrenRequest' ],
data() {
return {
text: 'this is value'
}
},
methods: {
request(){
console.log('work!');
this.$emit('received', this.text);
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="the-parent">
<h3>The children want me to say: {{ msg }}</h3>
<children #received="receivedFromChild"></children>
</div>

Proxy set a value

var context = {};
let head;
context.head = new Proxy({}, {
get(obj, prop) {
if (!head) {
head = {
htmlAttrs: {
lang: 'Fr'
}
}
}
if (prop === 'htmlAttrs') {
return `${JSON.stringify(head.htmlAttrs)}`
}
},
set(obj, prop, value, rec) {
return Reflect.set(...arguments);
}
})
context.head.htmlAttrs = {
key: true
}
console.log(context.head.htmlAttrs)
Now it log just lang: 'Fr' how to get it to log key: truetoo
In this case, the obj variable returned by get() contains them:
var context = {};
let head;
context.head = new Proxy({}, {
get(obj, prop) {
if (!head) {
head = {
htmlAttrs: {
// Include the properties
...obj.htmlAttrs,
lang: 'Fr'
}
}
}
if (prop === 'htmlAttrs') {
return `${JSON.stringify(head.htmlAttrs)}`
}
const text = prop in head ? head[prop].text() : ''
return text && prop.endsWith('Attrs') ? ` ${text}` : text
},
set(obj, prop, value, rec) {
return Reflect.set(...arguments);
}
})
context.head.htmlAttrs = {
key: true
}
console.log(context.head.htmlAttrs)

How to render unknown/variable number of React elements

I have this React component that looks like this:
var TestResult = React.createFactory(React.createClass({
displayName: 'test-result',
getInitialState: function getInitialState() {
return {
active: false,
testLines: [] //this value will update after AJAX/WebWorker completes
};
},
makeNewState: function makeState(data) {
this.setState(data);
},
componentDidMount: function () {
var self = this;
var testPath = this.props.testPath;
setTimeout(function(){
$.get(testPath).done(function (msg) {
var myWorker = new Worker('/js/workers/one.js');
myWorker.postMessage(msg);
myWorker.onmessage = function (msg) {
self.setState({testLines: msg.data.testLines});
};
}).fail(function (err) {
console.error(err);
});
}, Math.random()*2000);
},
render: function() {
var self = this;
return React.createElement('p', {
className: this.state.active ? 'active' : '',
onClick: this.clickHandler,
},
self.state.testName, ' ', self.state.testLines, React.createElement('b', null, 'Pass/fail')
);
}
}));
what I want is to render a variable number of components in the render function - the variable number is related to number of elements in the testLines array.
So, in order to do that, I might try changing the render method above:
render: function() {
var self = this;
return React.createElement('p', {
className: this.state.active ? 'active' : '',
},
self.state.testName, ' ', React.createElement('div', self.state.testLines, React.createElement('b', null, 'Pass/fail')
);
}
so what I am trying to do is pass testLines, which is an array of React.createElement results, to React.createElement. Is this the correct way of doing it? "It" meaning rendering a variable number of React elements.
What you have to do is map over the array and create each of the sub-elements, then render those:
render: function() {
var lines = this.state.testLines.map(function(line, i) {
// This is just an example - your return will pull information from `line`
// Make sure to always pass a `key` prop when working with dynamic children: https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
return (
<div key={i}>I am a line!</div>
);
});
return (
<div id='lineContainer'>
{lines}
</div>
);
};

Categories

Resources