How to design a multi tab chat with react/redux? - javascript

I am developing a chat platform to take multiple chats and he can switch between the chats.
var MessageList = React.createClass({
render() {
return (
<div className='messages'>
<h2> Conversation: </h2>
{
this.props.messages.map((message, i) => {
return (
<Message
key={i}
user={message.user}
text={message.text}
/>
);
})
}
</div>
);
}})
Let's just take an example of message list which will change when user switches the chat tab. Re rendering the same component with new messageList with respect to the chat makes sense but when there are 100 other component changes like this when there is a switch in chat, then there will be lot of repainting/rendering.(I know only diff will be changed in the dom but still.)
I would like to create different elements for different chats and hide and show them based on active chat. But react works under a single dom and replaces the dom with what's returned where it has been attached to.
React.render(<ChatApp/>, document.getElementById('app'));
Can anyone help me up with the design here?
Thanks in advance.

var Tabs = React.createClass({
displayName: 'Tabs',
propTypes: {
selected: React.PropTypes.number,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired
},
getDefaultProps: function () {
return {
selected: 0
};
},
getInitialState: function () {
return {
selected: this.props.selected
};
},
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps || this.state !== nextState;
},
handleClick: function (index, event) {
event.preventDefault();
this.setState({
selected: index
});
},
_renderTitles: function () {
function labels(child, index) {
var activeClass = (this.state.selected === index ? 'active' : '');
return (
<li key={index}>
<a href="#"
className={activeClass}
onClick={this.handleClick.bind(this, index)}>
{child.props.label}
</a>
</li>
);
}
return (
<ul className="tabs__labels">
{this.props.children.map(labels.bind(this))}
</ul>
);
},
_renderContent: function () {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
},
render: function () {
return (
<div className="tabs">
{this._renderTitles()}
{this._renderContent()}
</div>
);
}
});
var Pane = React.createClass({
displayName: 'Pane',
propTypes: {
label: React.PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
var App = React.createClass({
render: function () {
return (
<div>
<Tabs selected={0}>
<Pane label="Tab 1">
<div>This is my tab 1 contents!</div>
</Pane>
<Pane label="Tab 2">
<div>This is my tab 2 contents!</div>
</Pane>
<Pane label="Tab 3">
<div>This is my tab 3 contents!</div>
</Pane>
</Tabs>
</div>
);
}
});
ReactDOM.render(<App />, document.querySelector('.container'));
* {
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
body {
font: 300 14px/1.4 'Helvetica Neue', Helvetica, Arial, sans-serif;
background: #eee;
margin: 0;
padding: 0;
}
.tabs {
margin: 25px;
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 3px;
}
.tabs__labels {
margin: 0;
padding: 0;
}
.tabs__labels li {
display: inline-block;
}
.tabs__labels li a {
padding: 8px 12px;
display: block;
color: #444;
text-decoration: none;
border-bottom: 2px solid #f5f5f5;
}
.tabs__labels li a.active {
border-bottom-color: #337ab7;
}
.tabs__content {
padding: 25px;
}
<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 class="container"></div>

You are saying redux, so I'll try to give some insight but too lazy to provide any code, since it'll be too big / complex.
Beforehand, if needed, you can use normalizr when handling with nested JSON format since redux love to be immutable and nested makes it harder to be immutable.
The reducers:
chats { userid:"", message:"", time:"" },
users { userid:"", name:"" },
app { selectedUserId:"" }
Now, the number of tabs to be rendered / displayed is equals to number of users. The selected tab is based on app.selectedUserId. The message rendered in panel will be chats, which userid equals app.selectedUserId. Some snippet:
var lo = require('lodash');
var selectedChats = lo.filter(this.props.chats, k => k.userid == app.selectedUserId);
var messagesDom = selectedChats.map(k => <MessageLine chat={k});
var chatTabsDom = this.props.users.map(k => <ChatTab userid={k.userid} className={ k.userid == app.selectedUserId ? "active" : "" }> );
return <div>
<Tabs>{chatTabsDom}</Tabs>
<Messages>{messagesDom}</Messages>
</div>;

Related

How to call a function with arguments onclick of a button in a react component

Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?

Prevent focus on Expand More button after content is inserted in React

I need to list out a long name list inside my page while showing all names at first is not desirable.
So I try to add an expand more button on it.
However, using a button will keep the browser focus on that button after it's pressed, left the button position unchanged on the screen while the name was inserted before that button.
On the other hand, using any, not focusable element (eg. div with onclick function) will do the desired behavior but lost the accessibility at all. Making the "button" only clickable but not focusable.
How do I make the button flushed to list bottom like the snippet div block does? Or is there a better choice to expand the existing list?
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Removing focus from the button in the click handler is probably the most elegant approach: e.target.blur(). It will work on any HTML element, whether it is focusable or not (as with the div in your case).
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
e.target.blur()
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Inspired by #MiKo, temporally unmount the button after click and set a timeout to add it back seems to do the work. Since browser lose the focus on original expand button, this will keep content flush down without focusing the original button:
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const [showBtn, setShowBtn] = React.useState(true)
const handleExpand = e => {
setShowBtn(false)
setIdx(idx + 1)
setTimeout(() => setShowBtn(true), 10)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
{showBtn?
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div> :
<div></div>
}
</div>
}
But I'm still looking a method that doesn't need to 'unmount' a thing which should be there all time.

Is it possible to pass dynamic props, from one page to another with next.js?

I'm new to Next and have been trying to make a page(index.js) that fetches data(countries) and then displays that data, where each returned element(country) has a button to go to a page(info.js) where that specific countries data will be displayed, was wondering if its possible to pass the props(all country data) to the info.js page? I've tried reading the documentation and watching YT videos but can't seem understand what i'm reading/watching.
index.js:
import Link from 'next/link'
Welcome.getInitialProps = async function (props) {
const res = await fetch('https://restcountries.eu/rest/v2/all')
const data = await res.json()
return {
data: data
}
}
const MyLink = props => {
return (
<p>
<Link href={`/info?name=${props.name}`} >
<a>Learn More</a>
</Link>
</p>
)
}
function Welcome(props) {
return (
<div>
<div className="main-content">
<style jsx>{`
.main-content {
width: 80%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 5px;
}
.item {
border: 1px solid black;
text-align: center;
}
.item ul{
padding: 0;
}
.item ul li {
list-style-type: none;
}
`}</style>
{props.data.map(country => (
<div key={country.numericCode} className="item">
<h4>{country.name}</h4>
<p>Region: {country.region}</p>
<p>Population: {country.population}</p>
<MyLink name={country.name} borders={country.borders} currencies={country.currencies}/>
</div>
))}
</div>
</div>
)
}
export default Welcome
info.js:
import { withRouter } from 'next/router'
import Link from 'next/link'
const Info = (props) => {
return (
<div>
<h1>{props.router.query.name}</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
)
}
export default withRouter(Info)
In MyLink component instead of using Link you can create a normal div (style it like a link) and onClick of that div push it to different page using nextjs router:
//import useRouter
import { useRouter } from 'next/router'
//then call it
const router = useRouter()
const MyLink = props => {
return (
<p onClick={() => {
router.push({
pathname: `/info?name=${props.name}`,
query: { data: //data to pass },
})
}}>
<a>Learn More</a>
</p>
)
}
You can access that data in the location object in the query key
import {useLocation} from ""
const location = useLocation()
const data = location.query

Display user's initials on top of an SVG in ReactJS

I need the user's initials to appear on the avatars (the one in the menu is an SVG), just a grey circle with those initials. I have the function but now I have no idea how to call it in the JSX of the Dropdown menu (which is a SemanticUI library). Any cues?
const textToImage = require('text-to-image')
componentWillMount() {
let P = "", N = ""
if (res.data.Item.firstName && res.data.Item.firstName.length > 0) P = res.data.Item.firstName.charAt(0).toUpperCase()
if (res.data.Item.lastName && res.data.Item.lastName.length > 0) N = res.data.Item.lastName.charAt(0).toUpperCase()
this.setState({
initials: P + N
}, () => {
textToImage.generate(this.state.initials, { maxWidth: 30, maxHeight: 30 })
.then(dataUri => {
this.setState({ avatarInitiales: dataUri })
})
});
})
}
render() {
let avatarImage;
let userInitials;
let nomComplet;
if (this.state.user) {
avatarImage =
this.state.user.avatarImage === null || this.state.user.avatarImage === "image.jpg"
? (!this.props.pochette ?
"data:image/png;base64," + biquetteBase64
: "https://images-publiques.s3.amazonaws.com/avatar.png")
: `https://smartsplit-images.s3.us-east-2.amazonaws.com/${this.state.user.avatarImage}`;
userInitials =
this.state.user.avatarImage === null ? this.state.initials : null;
nomComplet = this.state.user.artistName
? this.state.user.artistName
: `${this.state.user.firstName} ${this.state.user.lastName}`;
}
let menu = (
<Dropdown text="" icon="angle down big black">
<Dropdown.Menu icon="down small">
<Dropdown.Item
content={nomComplet}
text={this.state.initials} //Not sure what to do around here
image={<AvatarInitialsSVG />}
/>
</Dropdown.Menu>
</Dropdown>
);
return (
<>
<div className="ui five wide column avatar--image profile"></div>
{nomComplet}
</Label>
//And here
{!userInitials && (
<img src={avatarImage} alt="user--avatar" className="user--img" />
)}
{menu}
</>
);
}
You probably need to add custom HTML + CSS, and re-use some of the classes from Semantic-UI.
Try something like this:
<Dropdown.Item>
<React.Fragment>
<div className="custom-initials-holder">
<AvatarInitialsSVG/>
<span className="custom-initials">{this.state.initials}</span>
</div>
<span className="text">{nomComplet}</span>
</React.Fragment>
</Dropdown.Item>
And your custom CSS overwrites:
.custom-initials-holder {
display: inline-block;
width: 24px;
height: 24px;
margin-right: 12px;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.custom-initials-holder > svg {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
Tweak according to your needs.
Small demo

Vue 2 event listener on component root

I'm trying to capture an event on the component root node, but the following does not work. I don't want to just listen on a node in the component. I want to be able to click on any element and then hit backspace to remove it. The code below is a basic example of how I setup my code.
<template>
<div v-on:keydown.delete="delete()">
<img id="image" src="..." v-on:click="set_active()">
</div>
</template>
<script>
export default {
return {
data() {
active: ''
},
methods: {
delete(){
delete this.$refs[this.active][0];
},
set_active() {
this.active = event.target.getAttribute('id');
}
}
}
}
</script>
After doing some tests, here is what I discovered:
Having a method called delete won't work. I don't know why, the question remains unanswered here. Rename it to remove, for example.
When trying to catch keyboard events on a div, you may need to add a tabindex attribute for it to work. (See here)
Interactive demo
Vue.component('my-component', {
template: '#my-component',
data() {
return {
images: [
"https://media.giphy.com/media/3ohs7KtxtOEsDwO3GU/giphy.gif",
"https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif",
"https://media.giphy.com/media/8L0xFP1XEEgwfzByQk/giphy.gif"
],
active: null
};
},
methods: {
set_active(i) {
this.active = i;
},
remove() {
if (this.active !== null) {
this.images = this.images.filter((_, i) => i !== this.active);
this.active = null;
}
}
}
});
var vm = new Vue({
el: '#app'
});
div {
outline: none; /* Avoid the outline caused by tabindex */
border: 1px solid #eee;
}
img {
height: 80px;
border: 4px solid #eee;
margin: .5em;
}
img:hover {
border: 4px solid #ffcda9;
}
img.active {
border: 4px solid #ff7c1f;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<my-component></my-component>
</div>
<template id="my-component">
<div #keydown.delete="remove" tabindex="0">
<img
v-for="(img, i) in images"
:key="i"
:src="img"
:class="{ active: active === i }"
#click="set_active(i)"
/>
</div>
</template>

Categories