React Portal input change removes elements - javascript

I've created this pen to demo it: https://codepen.io/no1melman/pen/WWyJqQ
essentially I have this portal:
const ChatArea = ({ children }) => {
const el = document.createElement("div");
el.classList.add('chatbox')
const root = document.getElementById("rootus");
useEffect(() => {
root.appendChild(el);
return () => root.removeChild(el);
}, []);
return createPortal(children, el);
};
And Use like:
const ChatBox = () => {
const [ reply, setReply ] = useState('');
const handleReply = (e) => {
e.preventDefault();
setReply(e.target.value);
}
return (
<ChatArea>
<div className="chat-title">Bot Convo</div>
<div className="chat-convo"></div>
<div className="chat-reply">
<input type="text" value={reply} onChange={handleReply} />
<button>Send</button>
</div>
</ChatArea>
)
}
For some reason as you start typing the body of the chatbox seems to disappear... I've put logs into the useEffect to see if that was causing it, but it didn't show what I thought

There are two issues here:
The first issue is
useEffect(() => {
root.appendChild(el);
return () => root.removeChild(el);
}, []);
Now as per the hooks principle the dependency should match the used variables inside the hook. If not used react will not run the effect next time.
SO in your case when you click on open chat it opens up the chat box. the effect ran and rendered the portal with the input box.
When you have typed the first letter and onChange happened
It triggered the rerender of ChatArea, which should have ideally run the effect again, but didn't run as dependency array is blank and react has not idea as to when re-render.so the effect ran once for the first time where chatArea ran UI mounted and next time, the effect did not run as dependency array is blank.
This line :
return createPortal(children, el); // is referring to the new el which is created
but not attached to DOM. Hence nothing is visible on UI inside chatbox.
Refer this link: do not miss dependencies React hooks FAQs sections they are great :)
2nd issue:
Ideally, new div should not be created every time. Persist the "div" element across consecutive rerenders
See this implementation: I know there can be other ways of implementing it.
Feedbacks are welcome.
const {
render,
createPortal
} = ReactDOM;
const {
useState,
useEffect,
useRef
} = React;
const ChatArea = ({
children
}) => {
const el = document.createElement("div");
el.classList.add('chatbox')
// This el above will be different in each render
// root will remain same, ideally root and chatdiv should be passed as props
const root = document.getElementById("rootus");
// this val and setVal is done to toggle render the chart area after
// chatDiv is updated
const [val, setVal] = useState(true)
const chatDiv = useRef(null)
// First useEffect to persist the div
useEffect(() => {
if (!chatDiv.current) {
chatDiv.current = el
setVal(!val)
}
}, [chatDiv])
useEffect(() => {
root.appendChild(chatDiv.current);
return () => {
return root.removeChild(chatDiv.current)
}; // you are removing it
}, [chatDiv, root]);
if (chatDiv.current) {
return createPortal(children, chatDiv.current)
}
return null
// In your case as the return happened first and found out the el
};
const ChatBox = () => {
const [reply, setReply] = useState('');
const handleReply = (e) => {
e.preventDefault();
setReply(e.target.value);
}
return ( <
ChatArea >
<
div className = "chat-title" > Bot Convo < /div>
<
div className = "chat-convo" > < /div>
<
div className = "chat-reply" >
<
input type = "text"
value = {
reply
}
onChange = {
handleReply
}
/> <
button > Send < /button> <
/div> <
/ChatArea>
)
}
const NavBar = ({}) => ( <
div className = "navbar" >
<
div > Home < /div> <
div > Somewhere < /div> <
/div>
);
const Main = () => {
const [showChat, setShowChat] = useState(false);
const openChat = () => {
setShowChat(true);
};
const chatterbox = showChat ? ( < ChatBox / > ) : null;
return ( <
div className = "container" >
<
h2 > Main < /h2> <
p >
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.The point of
using Lorem Ipsum is that it has a more - or - less normal distribution of
letters, as opposed to using 'Content here, content here', making it look like readable English.Many desktop publishing packages and web page editors now use Lorem Ipsum as their
default model text, and a search
for 'lorem ipsum'
will uncover many web sites still in their infancy.Various versions have evolved over the years, sometimes by accident, sometimes on purpose(injected humour and the like). <
/p> <
p style = {
{
display: "flex",
justifyContent: "center"
}
} >
<
button onClick = {
openChat
} > Open Chat < /button> <
/p> <
p style = {
{
display: "flex",
flexDirection: "column",
justifyContent: "center",
backgroundColor: "red"
}
} >
{
chatterbox
} < /p> <
/div>
);
};
const App = ({}) => ( <
div className = "app" >
<
NavBar / >
<
Main / >
<
/div>
);
render( < App / > , document.getElementById("rootus"));
body {
font-family: Raleway;
}
* {
box-sizing: border-box;
}
#rootus {
position: relative;
height: 100vh;
display: flex;
justify-content: center;
}
.navbar {
display: flex;
justify-content: center;
}
.navbar>div {
padding: 10px;
}
.navbar>div:hover {
background-color: gray;
cursor: pointer;
}
.container {
width: 960px;
}
.app {
display: flex;
flex-direction: column;
align-items: center;
}
.chatbox {
width: 400px;
height: 200px;
position: absolute;
bottom: 0;
border: 2px solid black;
background: white;
display: flex;
flex-direction: column;
}
.chat-title {
background: black;
color: white;
}
.chat-convo {
flex: 1;
display: flex;
}
.chat-reply {
display: flex;
border-top: 1px solid black;
}
.chat-reply>input {
width: 80%;
padding: 8px;
border: none;
outline: none;
}
.chat-reply>button {
outline: none;
border: none;
flex: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="rootus">
</div>
Ui was not coming up proper in the stackoverflow code snippet, so I
had to edit somethings in styling. you can have a look at code pen
codepen linkaccording to your original styling

Related

How to modify the code to not get appendchild error

I have been trying to use the below code but whenever I am trying to run it in react file I am getting this error
"Uncaught TypeError: Cannot read properties of null (reading 'appendChild')" error .
At first I found a design from codepen, then I tried to integrate it in react code but error is enevitable
Thus I am in need of finding answer to it
const container = document.getElementById('container');
const circlesArr = [];
for(let i=0; i<15; i++) {
circlesArr[i] = [];
for(let j=0; j<15; j++) {
const circle = document.createElement('div');
circle.classList.add('circle');
container.appendChild(circle);
circlesArr[i].push(circle);
}
}
function growCircles(i, j) {
if(circlesArr[i] && circlesArr[i][j]) {
if(!circlesArr[i][j].classList.contains('grow')) {
circlesArr[i][j].classList.add('grow');
setTimeout(() => {
growCircles(i-1, j)
growCircles(i+1, j)
}, 100)
setTimeout(() => {
circlesArr[i][j].classList.remove('grow');
}, 300);
}
}
}
circlesArr.forEach((15, i) => {
cols.forEach((circle, j) => {
circle.addEventListener('click', () => {
growCircles(i, j);
});
});
});
return (<div id="container" className="container"></div>)}
The CSS code
.container {
display: flex;
flex-wrap: wrap;
width: 450px;
}
.circle {
background-color: #5295F1;
height: 14px;
width: 14px;
transition: transform 0.3s linear;
}
.circle.grow {
transform: scale(2);
}
I assume the code before return is inside your component's body. It means you are trying to get an element with id "container" before rendering it so at that point the document.getElementById('container') returns null which doesn't have appendChild method.

Add clicks when differents div are clicked

Im working on a project and i have basically some troubles with things for my website.
This one is a bit hard for me, i have some ideas but i dont know how to do them in my javascript code.
I have 98 divs (it's a calendar where you can select everyday differents hours to book slots).
There is a Summary (kinda same thing on commercial website) which i want that it says how many slots you selected. But the problem is that i have like I said 98div so i wanna do it in one function.
On the slots you want to book, you can click on it (it selects it) and if you click on it again it deselects it.
I want that you can select as many slots that you want, and the summary shows how many you selected then you can go to next step.
Here is my code if you guys have some ideas !
function x1(e) {
var target = e.target,
count = +target.dataset.count;
target.style.backgroundColor = count === 1 ? "#707070" : 'black';
target.dataset.count = count === 1 ? 0 : 1;
target.innerHTML = count === 1 ? '' : 'réserver';
target.classList.toggle('Resatxt');
target.classList.toggle('unselectable');
}
Actually this code is for the selection of the slots (it's going background black when you clicl on it, and then back to the normal color when you deselect it).
But i think i can do what i want with this.
I thinked about incrementing +1 when we click on the div but the problem that i dont know how to figure it out is when you deselect it you have to do -1 but im a bit lost.
I tried to be clear but ik that is not really.
If you guys have some ideas, go for it.
Thanks a lot
it's nice to see that your joining the programming community. I hope I understood you correctly and made a simple and minimal example to present you how can you achieve what you want. This is just an idea, don't take it too serious and write your own logic to handle the functionality!
const divs = 98;
const list = document.querySelector("#list");
const selectedList = document.querySelector("#selectedList");
let selected = [];
let elementsAdded = 1;
const onSelectDiv = (element) => {
const elementCopy = element.cloneNode(true);
elementCopy.id += "-copy";
selected = [
...selected,
{
id: elementsAdded,
elementId: element.id
}
];
elementsAdded += 1;
selectedList.appendChild(elementCopy);
};
const onClick = (e) => {
if (e.target.className.includes("selected")) {
e.target.classList.remove("selected");
elementsAdded -= 1;
const elementToDelete = selected.findIndex(
(x) => e.target.id === x.elementId
);
selectedList.removeChild(selectedList.childNodes[elementToDelete + 1]);
selected = selected.filter((x) => x.elementId !== e.target.id);
return;
}
onSelectDiv(e.target);
e.target.className += " selected";
};
for (let i = 0; i < divs; i++) {
const div = document.createElement("div");
div.innerHTML += i;
div.className += "div";
div.id = i;
div.addEventListener("click", function (event) {
onClick(event);
});
list.appendChild(div);
}
.view {
display: flex;
flex-direction: 'column';
}
#list {
display: flex;
width: 400px;
max-width: 500px;
flex-wrap: wrap;
}
.div {
padding: 5px;
background-color: black;
cursor: pointer;
color: white;
border-radius: 10px;
margin: 10px;
}
.selected {
background-color: red;
color: white;
}
<div class="view">
<div>
<p>Elements:</p>
<div id="list">
</div>
</div>
<div>
<p>Selected:</p>
<div id="selectedList">
</div>
</div>
</div>

How render two different contents of a modal window?

Good afternoon, I have a modal window in which the content is displayed depending on the condition.
I can't to do when the subscription is disabled, the content of the modal changed to "Subscription successful disabled".
And when you click on the "Enable subscription" button, the content of the modal changed to "Enable subscription by clicking on the button"
Now it turns out that "Subscription successful disabled" and "Enable subscription by clicking on the button" are displayed at once.
The enableSub and disableSub functions emulate sending data to the server, and the state emulates receiving data from the server.
const {
useState
} = React;
const state = {
isSubscriptionEnabled: true,
}
const enableSub = () => {
state.isSubscriptionEnabled = true
}
const disableSub = () => {
state.isSubscriptionEnabled = false
}
function App() {
const [isOpen, setIsOpen] = useState(false)
return ( <
div > {
isOpen &&
< div className = "modalWrapper" >
<
div className = "modal" >
<
button onClick = {
() => setIsOpen(false)
} > close < /button> <
div > {
state.isSubscriptionEnabled &&
< div >
Are you sure disable subscription ?
<
button className = "button"
onClick = {
disableSub
} >
Disalbe subscription <
/button> <
/div>
} {
!state.isSubscriptionEnabled &&
< div >
Subscription successful disabled <
button onClick = {
() => setIsOpen(false)
} > Thanks < /button> <
/div>
} {
!state.isSubscriptionEnabled &&
< div >
Enable subscription by clicking on button <
button className = "button"
onClick = {
enableSub
} >
Enable subscription <
/button> <
/div>
} <
/div> <
/div> <
/div>
}
{
state.isSubscriptionEnabled &&
< button className = "button"
onClick = {
() => setIsOpen(true)
} >
Disalbe subscription <
/button>
} {
!state.isSubscriptionEnabled &&
< button className = "button"
onClick = {
() => setIsOpen(true)
} >
Enable subscription <
/button>
} <
/div>
)
}
ReactDOM.render( < App / > , document.getElementById('app'));
.app {
position: relative;
}
.button {
margin: 20px;
}
.modalWrapper {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.modal {
width: 300px;
height: 150px;
padding: 30px;
position: absolute;
border: 1px solid teal;
background: #fff;
}
<div id="app"></div>
jsfiddle
import {React,useState} from 'react'
function App() {
const [isOpen, setIsOpen] = useState(false);
const [isSubscriptionEnabled, enableSubscription] = useState(false)
const enableSub = () => {
enableSubscription(true)
}
const disableSub = () => {
enableSubscription(false)
}
return (
<div> {
isOpen &&
<div className = "modalWrapper" >
<div className = "modal" >
<button onClick = {
() => setIsOpen(false)
} > close </button>
<div> {
isSubscriptionEnabled&&
<div>
Are you sure disable subscription ?
<button className = "button"
onClick = {
disableSub
} >
Disable subscription
</button>
</div>
} {
!isSubscriptionEnabled&&
<div>
Subscription successful disabled
<button onClick = {
() => setIsOpen(false)
} > Thanks </button>
</div>
} {
!isSubscriptionEnabled&&
<div>
Enable subscription by clicking on button
<button className = "button"
onClick = {
enableSub
} >
Enable subscription
</button>
</div>
} </div>
</div>
</div>
}
{
isSubscriptionEnabled&&
<button className = "button"
onClick = {
() => setIsOpen(true)
} >
Disable subscription
</button>
} {
!isSubscriptionEnabled&&
<button className = "button"
onClick = {
() => setIsOpen(true)
} >
Enable subscription
</button>
}
</div>
)
}
ReactDOM.render( < App / > , document.getElementById('app'));
React components only render when we change a component using its useState inside that particular component. it will not rerender on your global variable
Please try to format your code at least before posting here,

ReactJS - How to have a string with multiple colors for each characters

I am new to ReactJs and Javascript and I'm writing a website with create-react-app.
I want to have the title of my homepage to show in different color-blocks, with a cutting point decided a priori. E.g. if the cutting point is set to 9 and the length of the title returned by String.length is 63, each color-block will show a different color every 7 chars.
I suspect the problem I am encountering is related to JSX syntax, since the issue is not happening with the same approach with vanilla Javascript. Any elucidation on this point is much appreciated.
Approach
I created a new functional component called ColorText which handles the coloring of the string. This component is called in the render() method of my main class App, and it is responsible of returning the colored version of the original string passed by the parent class.
import React from 'react';
import styled from 'styled-components';
import { GlobalStyle } from './styles/global_style.js';
import { ColorText } from './components/ColorText.js';
import morning_gif from './assets/morning3D.gif';
const TitleStyle = styled.h1`
font-family: BethEllen;
font-size: 30pt;
line-height: 60px;
display: flex;
text-align: center;
`;
const MorningGif = styled.img`
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
`;
export default class App extends React.Component {
state = {
message: "This is some example text, and it will be colorful as a rainbow!",
colorBreakPoint: 9,
colorScheme: {
pink: 'rgb(255,180,172)',
green: 'rgb(103,145,134)',
blue: 'rgb(38,78,112)'
}
}
render(){
return (
<div>
<GlobalStyle />
<div>
<h1>
<TitleStyle>
<ColorText content={this.state} />
</TitleStyle>
</h1>
</div>
<MorningGif src={morning_gif} alt="Loading the coffee..." />
</div>
);
}
}
Below is the ColorText component.
export const ColorText = ({ content: { message, colorBreakPoint, colorScheme } }) => {
const colorSpan = (message.length % colorBreakPoint) === 0 ?
message.length / colorBreakPoint
: message.length / 5;
const colorKeys = Object.keys(colorScheme);
let currentColor = 0;
let colorMessage = [];
for (let i = 0; i < message.length; i++){
if ((i % colorSpan) === 0){
currentColor++;
}
if (currentColor === 3){
currentColor = 0;
}
colorMessage[i] = ("<span style={{ color: '"
+ colorScheme[colorKeys[currentColor]]
+ "' }}>" + message[i]
+ "</span>");
}
return colorMessage.join("");
}
The expected output is a string whose chars are enclosed by a <span></span> tag, with an inline attribute style specifying the color for each enclosed char (e.g. <span style={{ color: 'rgba(255,180,17)' }}>H</span><span style={{ color: 'rgba(103,145,134)' }}>I</span> etc).
Problem
However, this is the current output:
multicolors-string-output.png
The string is being rendered as a string itself, and the HTML-tagged content is not evaluated as JSX object. I tried enclosing the ColorText component in braces when called by App and the same with return {colorMessage.join("")} in ColorText, but nothing changes.
I googled several options and tried different approaches, but I guess the problem is I am not understanding JSX syntax - being new even to Javascript. Any explanation on this point is super helpful!
NOTE: Here is the Javascript reproduction of the ColorText component and its output https://jsfiddle.net/qz4Lxdak/.
You need to return an array with JSX elements.
let colorMessage = [];
for (let i = 0; i < message.length; i++){
if ((i % colorSpan) === 0){
currentColor++;
}
if (currentColor === 3){
currentColor = 0;
}
colorMessage[i] = (
<span style={{ color: colorScheme[colorKeys[currentColor]].toString() }}>
{message[i]}
</span>
);
}
return colorMessage;

Fit text within a container by reducing font-size and truncating text

I have to fit text within a div container which is square. If the text is too large to fit within the container, I have to reduce the font-size from 32px to 18px. And even if that is not fitting, I have to truncate the text with "...". It looks simple enough. I am using plain JavaScript / React.
There are some approaches that I am trying.
<div className="container" ref={c => { this.comp = c; }}>
{text}
</div>
If the clientHeight < scrollHeight, the text overflows, hence I reduce the font size.
if (this.comp.clientWidth < this.comp.scrollHeight) {
this.setState({ overflow: true });
}
I am setting a style on the container based on the state changes.
style={this.state.overflow ? { fontSize: 18 } : undefined}
I like to reduce to truncate the text if it still overflows. Not sure how to truncate the text.
Code snippet so far:
class App extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
if (this.comp.clientHeight < this.comp.scrollHeight) {
console.log('reducing font size from 32px to 18px');
this.setState({ overflow: true });
}
}
render() {
const { overflow } = this.state;
return (
<div className="container"
ref={c => { this.comp = c; }}
style={overflow ? { fontSize: 18 } : undefined}
>
This is a long text which wont fit within the container. Inspite of reducing the font size, it wont fit. And I want to truncate with ellipsis. It is not possible with text-overflow as it is multi-line text. How do I figure how many characters to truncate it to?
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.container {
display: inline-block;
width: 200px;
height: 200px;
background-color: #fafafa;
padding: 10px;
font-size: 32px;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
This can be done by updating the component text till it fits. And then finally setting the state. The code snippet is shown below.
class App extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
let overflow;
if (this.comp.clientHeight < this.comp.scrollHeight) {
overflow = true;
this.comp.style.fontSize = '18px';
}
let truncatedText;
while (this.comp.clientHeight < this.comp.scrollHeight) {
const words = this.comp.innerText.split(' ');
words.pop();
truncatedText = words.join(' ') + '...';
this.comp.innerText = truncatedText;
}
this.setState({
overflow,
truncatedText
});
}
render() {
const {
overflow,
truncatedText
} = this.state;
const text = 'This is a long text which wont fit within the container.Inspite of reducing the font size, it wont fit.And I want to truncate with ellipsis.It is not possible with textoverflow as it is multiline text.How do I figure how many characters to truncate it to.';
return ( <div className = "container"
ref = {
c => {
this.comp = c;
}
}
style = {
overflow ? {
fontSize: 18
} : undefined
}>
{truncatedText || text}
</div>
);
}
}
ReactDOM.render( <App /> , document.getElementById('root'));
.container {
display: inline-block;
width: 200px;
height: 200px;
background-color: #fafafa;
padding: 10px;
font-size: 32px;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />
Keep reducing the text till it fits and finally update the state.
let truncatedText;
while (this.comp.clientHeight < this.comp.scrollHeight) {
const words = this.comp.innerText.split(' ');
words.pop();
truncatedText = words.join(' ') + '...';
this.comp.innerText = truncatedText;
}

Categories