How to bind value to be displayed in html in React? - html

I just created my first test in React.
Following an example of a tutorial I have created several buttons that activate a function by which they receive the index of the selected button. The first button selected must show one of the operators of the aray and the next one the opposite, and so on...
const operators = ['+', '-'];
const placeHolder = 'o';
function Boxes(props){
return (
<AppContext.Consumer>
{context => {
const value = context.boxes[props.index];
const icon = value !== null ? operators[value] : placeHolder;
const isDone = icon !== placeHolder ? 'done' : '';
return (
<button className="box-active"
onClick={() => context.boxAct(props.index)}>
{operator}
</button>
)
}}
</AppContext.Consumer>
)
}
and here is de function
boxAct = (index) => {
if (this.state.boxes[index] === null) {
this.state.boxes[index] = '+';
}
}
How can I achieve this? Following the steps of the example I only get the placeholder value in all the buttons and I can't get them to change.
What am I doing wrong?
thanks for your help

Functional components are different from class components. As you can read from official documentation react components.
Your Boxes component is declared as functional component. this.state syntax is valid just inside class component. In that case correct way to update state would be to call this.setState(<newstate>) function avilable to all class components. You can read more here react state.
You can provide state to functional components via hooks. In particolar useState hook. Here is explained how to do useState hook.
In your case (functional component) you can dop like this:
import hook with:
import React, { useState } from 'react';
then you need to initialize boxes state like this
const [boxes, setBoxes] = useState(context.boxes)
The function you will set as onClick handler is:
boxAct = (index) => { if (boxes[index] === null) {
let newBoxes = boxes;
nexBoxes[index] = '+';
setBoxes(newBoxes);
}
}
Be aware also that you should not pass data via context api, context api is used to provide global information such as application language or theme. Read more here react context

Related

Conditionally make a page read-only using react

I want to create a React webpage that has both editable and read-only versions, the whole page not just a few elements on the page. A version is displayed to the user based on user id and other conditions. How do I do it?
The only straight forward way I know is to create 2 pages one editable and one read-only and based on the condition show the appropriate version (html page) to the user.
Is there a better and smarter way to do this? Like can I create just one page for both versions and toggle the mode based on the condition to the users?
Your question should have provided an example of some code you had tried but based on the description, very rough example below of one of many possible solutions.
Suppose EditView component is your page and you are able to pass a value for permission based on whatever credential you need to apply.
Then you have a component, ExampleField that takes the permission and displays either an input or static text. A collection of multiple of these fields is mapped from a theoretical array of data that you'll have to fetch from somewhere and the fields are returned by the main component.
const EditView = ({permission}) => {
const [editable, setEditable] = useState();
const [values, setValues] = useState([]);
useEffect(() => {
setEditable(permission);
}, [permission]);
useEffect(() => {
//maybe fetch your data from a back end or whatever and assign it to `values`
//on page load
}, [])
const ExampleField = ({permission, val, index}) => {
const handleChange = (e) => {
let vals = [...values];
vals[index] = val;
setValues(vals);
}
return(
<>
{permission
? <input name="example" type="text" defaultValue={val}
onChange={handleChange} />
: <span>{val}</span>}
</>
)
}
const fields = values.map((value, i) => {
return <ExampleField permission={permission} val={value} index={i}/>
})
return(
<>
{fields}
</>
)
}
Most likely, you'll want to break out various field components into their own file and, instead of using useState, you would probably want to explore useContext or useStore type functionality to lift up your state and do all the react things.
*Haven't tested or even compiled this code - for illustration purposes only.

How to change react component on someone else's onclick

I am building a React app where I render a family tree. For that, in each of the family tree component nodes, I have added a onclick which opens a modal (aka popup form) that allows the user to edit the info of that person. In that modal/popup, I have a submit button on the bottom. I want it so that when the submit button is clicked, the input fields in the form (ex: name, parents, etc..) are fetched and updated on the respective node in the tree. I tried this in my code:
submitbtn.onclick = () => {
alert("couple submit clicked!");
info.husband = document.getElementById("hname_inp").value;
info.wife = document.getElementById("wname_inp").value;
modal.style.display = 'none';
alert(info.husband + ' ' + info.wife)
};
return (
<li>
<div onClick={handleClick}>
<span className="male">{info.husband}</span>
<span className="spacer"></span>
<span className="female">{info.wife}</span>
</div>
<Children />
</li>
);
By default, the component shows the info passed through props. When the submit button is clicked, i want the data from the input fields to replace the data in the component. The onclick and the data is feteched fine, but the component is not updated. I am new to React so it might just be a silly mistake, please bare with me.
Finally, and this is a little of the topic, but when I click the submit button, the screen flickers for a second a html page with no formatting shows up then it goes back to normal. What might be the cause for that?
Edit (New Code):
import React from "react";
export default class Couple extends React.Component {
constructor(props) {
super(props);
this.state = {
husband: this.props.husband,
wife: this.props.wife,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const newState = this.state
const modal = document.getElementById('coupleModal');
modal.style.display = 'block';
const submitbtn = document.getElementById('couplesubmitbtn');
submitbtn.onClick = (event) => {
event.preventDefault()
modal.style.display = 'none'
newState.husband = document.getElementById('hname').value;
newState.wife = document.getElementById('wname').value;
}
this.setState(newState);
}
render() {
const children = this.props.children;
return (
<li>
<div onClick={this.handleClick}>
<span className="male">{this.state.husband}</span>
<span className="spacer"></span>
<span className="female">{this.state.wife}</span>
</div>
{children != null && children.length !== 0 ? <ul>{children}</ul> : ""}
</li>
);
}
}
I think you should use different onClick functions on every node.and plus you can change name of the husband using a modal.I have used prompt and saved the data in state for husband and wife
const [Husband, setHusband] = useState("Varun")
const [Wife, setWife] = useState("Alia")
const handleClick = (e) => {
e.preventDefault()
setHusband(prompt("Please enter your Husband Name:"))
};
const handleWife = (e)=>{
e.preventDefault()
setWife(prompt("Please enter your Wife Name:"))
}
return (
<li>
<div>
<span className="male" onClick={handleClick}>{Husband}</span>
<span className="spacer"></span>
<span className="female" onClick={handleWife}>{Wife}</span>
</div>
</li>
);
};
As mentioned in comments before it would be great if you could provide a fiddle etc to look at.
You mentioned that you are new to React so even at the risk of sounding stupid may I just ask are you using some sorf of state handling here? If not then it might be something to look into. If you're already familiar with React state this answer is pointless and should be ignored.
In reactjs.org there are great documentations about what is the difference between state and props?
setState() schedules an update to a component’s state object. When state changes, the component responds by re-rendering.
https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-state-and-props
So in this case information about your family tree would be initialized to state and popup should then update the state via setState. The new input then gets update and UI components rerender.
If I'm right and the state handling will help you go forward I would also recommend to look up React Hooks. Hooks are a new addition in React 16.8 and when you grasp an idea of state using Hooks will be a easy and more elegant way to write your application
==================== Part 2 ====================
Here's the answer to your question you asked below in comments and some additional thoughts:
I assume the flickering is actually page refreshing on submit. So catching the user event and passing it on and calling preventDefault() is a way to go. I will an example below.
Looking at your code I'm more and more convinced that you are indeed lacking the state handling and it's the initial problem here. You could really benefit reading little bit more about it. At the same time it will help you understand better the logic of how React generally works.
Here's another link that might be worth checking out:
https://www.freecodecamp.org/news/get-pro-with-react-setstate-in-10-minutes-d38251d1c781/
And lastly here's the codeSnippet. Note that the wifes input element you're trying to target with getElementById should be document.getElementById("hname") instead of document.getElementById("hname_inp")
submitbtn.onclick = (event) => {
event.preventDefault();
console.log(props.wife);
modal.style.display = "none";
info.husband = document.getElementById("name").value;
info.wife = document.getElementById("hname").value;
alert(info.husband + " " + info.wife);
};
==================== Part 3 ====================
Nice to see that you took a closer look on state handling and have tried it out. I would continue building the knowledge with some additional reading. Here's a good post about Reacts Data handling.
https://towardsdatascience.com/passing-data-between-react-components-parent-children-siblings-a64f89e24ecf
So instead of using state handling separately in different components I would suggest that you move it to App.js as it is the obvious Parent component of others. There you should also think about the data structure. I assume this project is not going to be connected (at least for now) for any api or database and so it's something that would be handled here as well.
So defining some sort of baseline to App.js could look for example like this.
this.state = {
state = { family : [
[{ name: 'kari', gender: male }]
[
{ name: 'jasper', gender: male },
{ name: 'tove', gender: femmale }
],
]
}
}
Then I suggest that you move the handlers here as well. Then writing them here you don't maybe even need separate ones to couples and singles any more.
I'm sorry to hear your still seeing the flickering. My best guess for this is that modal isn't aware about the event.preventDefault. For clarity I would refactor this a bit as well. Generally it's not a good practice to try to modify things via getElements inside React. It's usually all state and props all the way. So I added a few lines of code here as an example of how you could continue on
import React from "react";
import SingleModal from "./Modals/SingleModal";
export default class Couple extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
};
this.popUpHandler = this.popUpHandler.bind(this);
}
popUpHandler(event) {
event.preventDefault()
this.setState({visible: !this.state.visible})
}
render(props) {
return (
<>
<SingleModal visible={this.state.visible} popUpHandler={this.popUpHandler }/>
<li>
<div onClick={this.popUpHandler}>
<span className={this.props.gender}>{this.props.name}</span>
</div>
</li>
</>
);
}
}
And similary in SingleModal getting rid of the form submit like this:
<input
type="submit"
value="Submit"
className="submit"
id="singlesubmitbtn"
onClick={(e) => {
e.preventDefault();
props.popUpHandler(e)
}}
/>
PS. I think this is going to be my last answer on this question here. The answer is getting too long and it's starting to drift off topic of the original question. Good luck with your project

React Render HTML to Infowindow using #React-Google-Maps/api

How do I to render html in google maps Infowindow?
I have tried several different ways to do it, but none of them seems to work.
My code :
import React, { Component, useState } from 'react';
import ReactDOMServer from 'react-dom/server';
import { GoogleMap, LoadScript, useLoadScript, InfoWindow, Marker, } from '#react-google-maps/api'
export class MarkerWithInfoWindow extends React.Component {
constructor() {
super();
this.state = {
isOpen: false
}
this.onToggleOpen = this.onToggleOpen.bind(this);
this.onEventWindowClick = this.onEventWindowClick.bind(this)
}
onToggleOpen() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
const imgUrl = this.props.icon;
return (<Marker
title={this.props.title}
position={this.props.position}
icon={{
url: imgUrl,
}}
>
{this.state.isOpen && <InfoWindow
onCloseClick={this.onToggleOpen}
position={this.props.position}
onMouseOut={this.onToggleOpen}
pixelOffset={{ width: 25, height: 25 }}
zIndex={-1}
onClick={this.onEventWindowClick(this)}
>
<> {ReactDOMServer.renderToString(this.props.content)}</>
</InfoWindow>}
</Marker>)
}
}
my result looks like this:
I guess content prop is passed as HTML string, then dangerouslySetInnerHTML needs to be set to render it as html instead of returning it as a plain string, for instance:
const InfoWindowContent = () => <div dangerouslySetInnerHTML={{ __html: this.props.content }}></div>;
Usage
<InfoWindow
onCloseClick={this.onToggleOpen}
position={this.props.position}>
<InfoWindowContent/>
</InfoWindow>
Demo
I've done this only as a functional component. According to the Google Map API docs, the desired HTML content can be passed as the value of the content property of the InfoWindow instance at its instantiation. According to the documentation of the react-google-maps/api, the (initial) options can be passed as a prop in the InfoWindow JSX.
In my case, I recompute the InfoWindow content on the fly, so I keep that string as a piece of state (dataWindowContent in my case). React guarantees that the InfoWindow component will be rerendered each time the state changes, so that's how I keep mine current.
Finally, the InfoWindow component is created with the InfoWindow component open by default. I keep mine closed until it has something to show, so I put that behavior in the onInfoWindowLoad handler.
You'll need a ref to the underlying Google Maps API instance -- the InfoWindow component conveniently passes that object as an argument when it invokes the onLoad handler. So here's a sketch of how you might do what you're attempting (note that I haven't exercised this, I'm just trying to say what you might try):
const MarkerWithInfoWindow = ( {content, title, position, icon, ...props} ) => {
const [infoWindowContent, setInfoWindowContent] = useState(content);
const [localPosition, setLocalPosition] = useState(position);
const rawInfoWindowRef = useRef(null);
const onInfoWindowLoad = (aRawInfoWindow) => {
rawInfoWindowRef.current = aRawInfoWindow;
if (''===infoWindowContent) {
aRawInfoWindow.close();
}
};
return (
<GoogleMap>
<Marker
title={title}
position={localPosition}
icon={{url: icon}}
>
<InfoWindow
onLoad={onInfoWindowLoad}
onCloseClick={onInfoWindowClick}
options={{content: infoWindowContent}}
position={localPosition}
/>
</Marker>
<GoogleMap>
);
};
Note that I've passed your desired HTML into the InfoWindow instance as the value of its content parameter inside its option prop.
Note also that position and content props are used to initialize the corresponding local state. Use their setters to move the marker/infoWindow or move the map. Also note I've surrounded your Marker and InfoWindow with a containing GoogleMap. I'm pretty sure the components require this container. You might need set the center property of the containing map as another piece of state so that it changes if the position changes.
This approach lets you avoid the use of dangerouslySetInnerHTML. The only way to change infoWindowContent is to use its setter (setInfoWindowContent) and this will force a rerender of the component tree. In that re-render, the low-level InfoWindow instance will be updated with its new content automatically, and it will render the HTML.

Is it possible to copy an object's properties from one component and display them in another component using refs?

Say I have 2 components. One is a table with a list of stores. Each store has properties like color, item, open, closed. The other component is one to create a store.
I want to be able to click on a little copy icon on one of the created stores already, and take that information to the create store component, and populate that component with the properties in order to make changes and create a completely new store.
Is this doable using refs? Or is there a better way of doing this?
Use ref to this task is a mistake. React works using a Virtual DOM that is a cleaner and faster Object Tree with information that will be through to DOM by React DOM the REF API is used to access direct the DOM information, and you don't need any information from DOM to do ur task.
https://reactjs.org/docs/refs-and-the-dom.html
A way yo do what you describe is create a state/setState on the parent component and pass a state for the store component and a setState to the table component for example:
import React, { useState } from 'react'
const StoreComponenet = ({ color, item, open})=>{
// logic of component
return (
<div>
// ...
</div>
)
}
const TableComponent = ({ setStore })=>{
// logic of component
return (
<table>
<tr onClick={() => setStore("blue", {id: 2, name: "BlueStore" }, false)}>
Build blue store
</tr>
...
</table>
)
}
const App = ()=>{
const [store, setStore] = useState(null)
return (
<TableComponent setStore={setStore} />
{
store &&
<StoreComponent
color={store?.color}
item={store?.item}
open={store?.open}
/>
}
)
}

Angular 6 - How to stop infinite polling in subscribe()

So I want to show an icon based on whether or not the number of projects in my list is > 3. I am using this getProjects() function that I need to subscribe to in order to get the data. I am setting a boolean when I subscribe that checks the number of projects in the list, then in my HTML, I use a ngIf to show the icon based on the boolean. I am able to get it to show correctly, however, I think I am constantly polling in my subscribe, and setting this boolean over and over again because it is making my webpage run really slow.
I have already tried the take(1) method which doesnt seem to stop the subscription, as well as set it to a "this.variable" scope inside my component. I am currently using event emitters however that is not working either.
This is my code so far,
Function that I subscribe to (in a different component):
getProjects(): Observable<ProjectInterfaceWithId[]> {
const organizationId = localStorage.getItem('organizationId');
return this.firestoreService.collection('organizations').doc(organizationId)
.collection('projects').snapshotChanges()
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as ProjectInterface;
const id = a.payload.doc.id;
return {id, ...data} as ProjectInterfaceWithId;
})),
map(list => {
if (list.length !== 0) {
this.buildProjectLookup(list);
this.projects = list;
return list;
}
})
);
}
Function that i use to get the data and set the boolean:
#Input() toggle: boolean;
#Output() iconStatus = new EventEmitter();
displayIcon() {
this.projectService.getProjects()
.pipe(take(1))
.subscribe(
list => {
if(list.length >= 3){
this.toggle = true;
this.iconStatus.emit(this.toggle);
}
});
}
HTML:
<i *ngIf="displayIcon()" class="material-icons">list</i>
Is there any way for me to literally just check the list length once so I don't get caught in this subscription loop? Thank you in advance!
It looks like it could be happening due to the ngIf referring to the displayIcon() method.
Every time change detection runs within your component, this method will be called. If your component is using default change detection, this will be very often.
see https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/ for more
One way this could be fixed is by making the ngIf refer to a variable instead.
For example, you could set a projects$ observable using
this.projects$ = this.projectService.getProjects()
.pipe(
take(1),
tap(projects => this.iconStatus.emit(projects.length >= 3))
);
This observable should likely be instantiated in your ngOnInit() method.
Then in your template you can use
<i *ngIf="(projects$ | async)?.length >= 3" class="material-icons">list</i>