reusing the "add todo" component as "edit todo" in preact - javascript

I just copied the todo from w3 schools and added some functionality with preact. So far all working fine with functional components. Don't mind the string literals and the inline styles.
What I am not getting is:
I have the empty "form" for adding todos:
const EditTodo = (props) => {
return html`
<h2 style="margin:5px;">My To Do List</h2>
<input ref=${todoTitleRef} type="text" placeholder="Title..." value=${props.todo.title}/>
<textarea value=${props.todo.content} ref=${todoContentRef} style="margin-top: 20px;" type="text" id="myText" placeholder="Text..."></textarea>
`;
}
I wanted to reuse it for editing a todo. So my question is: How can I add a hook in the root component to re-render the EditTodo but passing the todo that was clicked to edit?
this is the main component:
return html`
<div class="app">
<div id="myDIV" class="header">
${html`<${EditTodo} todo=${{}}/>`}
<div style="margin-top: 40px;"><span class="addBtn" onClick=${addTodo}>Add</span></div>
</div>
<ul id="myUL">
${todos.map((todo) => html`<${TodoItem} todo=${todo}/>`)}
</ul>
</div>
`;

For anyone wondering,
added the const after the reducer:
const editingTodo = todos.filter(todo=>todo.isEditing);
then changed the prop todo of the main editTodo to:
${html`<${EditTodo} todo=${editingTodo.length>0?editingTodo[0]:{}}/>`}

Related

How do I make React onClick calls only affect a single item, instead of all?

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.
At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.
Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button type="button" className="playbutton">
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]); // using the React state for the cards array
const [showForm, setShowForm] = React.useState((false);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(!showForm)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}
</div>
);
}
export default Displaycards;
I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .
It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.
The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.
The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.
This of course won't work if you want to render the EditForm in a position relative to the "active" card.
[Edit]
Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.
If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.
const [showForm, setShowForm] = React.useState("");
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
// Rest of the JSX
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(id)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm == id && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}

Add one component multiply times on one page

How can I place same component on one page multiply times? I have input component who get props and do some stuff. I need to place more than one inputs in one page and i thought i can just copy/paste my component but i get error because vue is thinking that all of my components are the same dom element. how can I put them?
index.vue
<template>
<div class="container">
<Input name="name" />
<Input name="surname" />
<Input name="pass" />
</div>
</template>
Input.vue
<template>
<div class="inputs">
<label class="inputs__label" :for="name">Имя</label>
<input
v-click-outside="moveR"
class="inputs__input"
:name="name"
type="text"
#click="moveL($event.target)"
/>
</div>
</template>
<script>
import vClickOutside from 'v-click-outside'
export default {
directives: {
clickOutside: vClickOutside.directive,
},
props: ['name'],
methods: {
moveR(e) {
console.log(e)
e.classList.add('inputs__lable_r')
},
moveL(e) {
console.log(e)
e.classList.remove('inputs__lable_r')
},
},
}
</script>
iam sorry i dont have a big baggage of knowledge of vue and google doesnt gave me needed information
i write on nuxt but i think its same trouble with vue
This is what it should be
moveR(e) {
e.target.classList.add('inputs__lable_r')
},
You were missing a e.target, hence it was not targeting the HTML element but rather the event.

How do you create a reusable Vue component with d3?

I'm trying to create a Chart.vue component based on D3. I need to be able to add multiple instances to a single page and keep each one separate.
I've tried to assign an ID generated with uuid to the div wrapping my component in the template:
<template>
<div :id=this.id>
<svg></svg>
</div>
</template>
The ID is created when the component is created.
<script>
import * as d3 from "d3";
import { v4 as uuidv4 } from "uuid";
export default {
...
created () {
this.id = uuidv4()
},
...
The chart is re-rendered when there is an update to the data passed in as props from the parent App.vue. To select the "correct" <svg> element that is owned by a particular instance of Chart I use the unique this.id in my renderChart method:
methods: {
renderChart(chart_data) {
const svg_width = 1000;
const svg_height = 600;
const svg = d3
.select("#" + this.id)
.select("svg")
.attr("width", svg_width)
.attr("height", svg_height);
...
Proceeding to add all the axes, data, etc.
If I add two such components to my App.vue template:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart :issues="issues" />
<Chart :issues="issues" />
</div>
</template>
I see that they are added to the DOM with some uuid that's been created. When the data is updated and the renderChart function executes, both components get a copy of the "issues" data, but I only see one chart being created.
I'm quite novice with JavaScript, Vue and D3, so perhaps going about this the wrong way, but it seems like this should work?
Any help is appreciated.
Well, I seem to have found a solution, although I don't fully understand it, and I'm not sure why the initial approach didn't work (note: original approach did seem to work sometimes, but the behaviour was unpredictable).
To solve, I pass a unique ID to the component from the parent template as a prop and add it as the Chart component <div> id.
In App.vue:
<template>
<div id="app">
<form action="#" #submit.prevent="getIssues">
<div class="form-group">
<input
type="text"
placeholder="owner/repo Name"
v-model="repository"
class="col-md-2 col-md-offset-5"
>
</div>
</form>
<Chart id="chart1" :issues="issues" />
<Chart id="chart2" :issues="issues" />
</div>
</template>
Now I need to add id in the props of the Chart.vue and set a variable in the data() section.
<template>
<div :id=chart_id>
<svg></svg>
</div>
</template>
<script>
import * as d3 from "d3";
export default {
name: 'Chart',
props: ["issues", "id"],
data() {
return {
chart: null,
chart_id: this.id
};
},
...
I'm not sure why the uuid approach didn't work, but this seems more robust.

Render different html depending on index of object in react

In my meteor project I have a collection called auctions. Using react I wish to render 3 columns of this auctions with unlimited number of rows. To accomplish this I thought it would be possible to send the index of the object but I have no idea how to do this. Another problem is that it shows an error with the html code since I'm not closing the 'div' tag.
This is my App.js:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { withTracker } from 'meteor/react-meteor-data';
import { Auctions } from '../api/auctions.js';
import Auction from './Auction.js';
//App component - represents the whole app
class App extends Component {
renderAuctions() {
return this.props.auctions.map((auction, index) => (
<Auction key={auction._id} auction={auction} index={index} />
));
}
render() {
return (
<div className="container section">
<div className="row">
{this.renderAuctions()}
</div>
</div>
);
}
}
export default withTracker(() => {
return {
auctions: Auctions.find({}).fetch(),
};
})(App);
And my Auction.js:
import React, { Component } from 'react';
//Task component - resepresnts a single todo item
export default class Auction extends Component {
render() {
if(index % 3 === 0) {
return (
</div> /* Shows an erros here because of closing tag*/
<div className="row">
<div className="col s4 ">
<div className="card">
<div className="card-image">
<img src="images/logo.png" />
</div>
<div className="card-content">
<span className="card-title">
{this.props.auction.auctionName}
</span>
<p>
I am a very simple card. I am good at containing small bits of information.
I am convenient because I require little markup to use effectively.
</p>
</div>
<div className="card-action">
This is a link
</div>
</div>
</div>
);
} else {
<div className="col s4">
<h1>Brincoooo</h1>
<div className="card">
<div className="card-image">
<img src="images/logo.png" />
</div>
<div className="card-content">
<span className="card-title">
{this.props.auction.auctionName}
</span>
<p>
I am a very simple card. I am good at containing small bits of information.
I am convenient because I require little markup to use effectively.
</p>
</div>
<div className="card-action">
This is a link
</div>
</div>
</div>
}
}
}
Any time you return HTML from a render function it needs to be self contained and have balanced tags. That's the way React works, and why it's giving you an error.
Instead of trying to group 3 auctions at a time, you could think of using flexbox instead. With flexbox you simply render all of your auctions, and it looks after the wrapping automatically for you. Users with wider screens will see more than 3 columns, and users on mobile will see probably one when in portrait mode.
If you want to learn about flexbox, there is a cute tutorial here: https://flexboxfroggy.com/ There are plenty of tutorials around if you don't like that one, such as this: https://scotch.io/tutorials/a-visual-guide-to-css3-flexbox-properties
I'll let you do the work from here

Meteor/React: How to integrate react component in main template

This is how my search page looks like in my meteor application:
/client/main.html
<head>
<title>Search</title>
</head>
<body>
<div class="ui icon input">
<input type="text" placeholder="Search...">
<i class="circular search link icon"></i>
</div>
</body>
I'm completely new to react and I would like to use react for this simple search.
/imports/ui/search.jsx
import React, { Component } from 'react';
export default class Search extends Component {
render() {
return (
<div class="ui icon input">
<input type="text" placeholder="Search...">
<i class="circular search link icon"></i>
</div>
);
}
}
But how do I have to use it for react properly? How do I use that component in the main template? And how can I set this input field centered on the screen?
You'd better check the document and example first.
Here is an example to answer your question.
Html just contains the empty container element.
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
The App component is the main component. You could use something like Router.
class App extends React.Component {
render() {
return (
<div>
<Search />
</div>
);
}
}
Search component as you made. In above, you could see Search component is used such that way in React.
About style, you can pass style object to component directly or make a css file.
class Search extends React.Component {
render() {
return (
<div class="ui icon input" style={searchContainerStyle}>
<input type="text" style={searchInputStyle} placeholder="Search..." />
<i class="circular search link icon"></i>
</div>
);
}
}
const searchContainerStyle = {
width: '50%',
margin: '0 auto'
}
const searchInputStyle = {
width: '100%'
}
Finally, render App under container element.
React.render(<App />, document.getElementById('container'));

Categories