How to only rerender a new element in an array - React - html

I have a web app that uses a websocket to receive information from an API I have put together.
Everything works great, however, every time new information arrives from the websocket, the whole list on my frontend (React) is updated.
Here is the relevent code:
componentDidMount(prevState) {
socketIO.on('newNotification', (response) => {
const notifications = this.state.notifications;
console.log(response)
const newNotifications = response.data
this.setState(prevState => ({
notifications: [...this.state.notifications, newNotifications]
}))
});
}
notifications is a list of notifications that is received from my API, which I set to the state.notifications whenever a response is received.
My understanding is React only updates what it needs to, so I'm not sure what is going on.
Here is my Notification component:
import React from "react"
class Notification extends React.Component {
render(){
return(
<ul>
<li
key = {this.props.notification.id}
onClick={() => this.props.deleteNotificationProps(this.props.notification.id)}>
<div className='separator-container'>
<div className={'notification-border ' + this.props.notification.stat_abr}>
<div className='notification' >
<div className='left-notification'>
<div className = 'stat-abr'>{this.props.notification.stat_abr}</div>
<div className = 'game-time'>{this.props.notification.game_time_string}</div>
</div>
<div className='middle-notification'>
<div className='player-image'>
<img src={"http://nhl.bamcontent.com/images/headshots/current/168x168/" + this.props.notification.player_id.toString() + ".jpg"} alt="" className="player-img" />
</div>
</div>
<div className = 'right-notification'> {this.props.notification.description} </div>
</div>
</div>
</div>
</li>
</ul>
)
}
}
export default Notification
I tried various diferent methods of updating the state, but nothing seems to work.
EDIT: here is the NotificationList class where the Notification component is created:
class NotificationList extends React.Component {
render() {
return(
<ul>
{this.props.notifications.map(notification => (
<Notification
id = {notification.id}
notification = {notification}
handleChangeProps = {this.props.handleChangeProps}
deleteNotificationProps = {this.props.deleteNotificationProps}
/>
))}
</ul>
)
}
}

I can't see the code where you iterate over the notifications to create Notification components.
I assume you have a notifications.map(...) somewhere... To only re-render new components, use the key={...} attribute inside of the map, with a value unique to each attribute (use index if you don't have a unique key).
e.g.
<div>
{ notifications.map((notification) => <Notification
key={notification.id}
notification={notification}
/>)
}
</div>

Figured out what was wrong:
I had a <ul></ul> tag surrounding my <li></li> tag in my Notification class.
Removed this and all is working as it should.

Related

Trying to use Typed.js in react

I am trying to use Typed.js on my website, and I have been getting many errors I cant find a way to fix them.
I have looked through GitHub, and I have found the exact same problem that someone had I used that code in my document and the errors are still popping up. The errors are always Expression Expected and } expected also, Unexpected token. Did you mean {'}? Here is a picture of the errors too. Here is the errors that are resulting:
errors
import "./App.css";
import Typed from "typed.js";
import { useEffect, useRef } from "react";
function HomePage() {
return (
<>
<body>
<div class="hero">
<nav>
<h2 class="logo">JAKEISTHATMAN.</h2>
<ul>
<li>
Home
</li>
<li>
About
</li>
<li>
Services
</li>
<li>
Portfolio
</li>
</ul>
<button type="button">Contact</button>
</nav>
</div>
<div class="container"></div>
<div class="main-text">
<h1>
I'm a <span class="type"></span>
</h1>
<p>
Also I work for <b>#no</b> media team!
</p>
<button type="button">Contact</button>
</div>
export default function Typed() {
const TypedElement = useRef(null);
useEffect(() => {
if (!TypedElement.current) return;
const typed = new Typed(TypedElement.current, {
strings: [
"string",
"string2",
"string3",
],
startDelay: 300,
typeSpeed: 100,
backSpeed: 100,
backDelay: 500,
loop: true,
});
// Destroying
return () => {
typed.destroy();
};
}, []);
return <span ref={TypedElement}></span>
}
</body>
</>
);
}
export default HomePage;
First, if the code example above is how your code really is, it won't work because you just have javascript in an html tag all of a sudden. Try wrapping the javascript with curly brackets to let the file know you are typing javascript code now like this:
<div>
{console.log("this is javascript")}
</div>
Second, it looks like you are trying to define a functional component called Typed. At the very least, it is poor organization to define a functional component inside an html tag like you have done above (if it isn't impossible, not sure, never tried it). You should define the functional component called Typed outside the HomePage function, like so:
import "./App.css";
import Typed from "typed.js";
import { useEffect, useRef } from "react";
const Example = ({args_if_necessary}) => {
const typeTarget = useRef(null);
useEffect(() => {
const typed = new Typed(typeTarget.current, {
strings: ["<i>First</i> sentence.", `use ${args_if_necessary}`],
typeSpeed: 40,
});
return () => {
typed.destroy();
};
}, []);
return <span ref={typeTarget} />;
};
function HomePage() {
return (
<>
<body>
<your-other-html />
<Example args_if_necessary={"something"} />
</body>
</>
);
}
export default HomePage;

How to create autocomplete box using vue.js?

I am new at Vue, now I am creating simple search app (using Vue.js cdn).
I want to append suggestion bar which contains all user id's from fake JSON server, for example if I write into search bar 1, I want to append only user which id is 1, and then I click to that user id I want to send another request to receive only this user info.
I am stuck, how I can solve this?
var app = new Vue({
el: '#app',
data: {
message: '',
searchKey:'',
result:[]
},
methods:{
async getData() {
// GET request using fetch with async/await
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${this.searchKey}`);
const data = await response.json()
this.result = data
},
},
created(){
this.getData()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.js"></script>
<div id="app">
<div class="search-area">
<div class="header-wrapper">
<h1>Tag Search</h1>
</div>
<div class="search-bar-custom">
<input placeholder="Search tags" v-model="searchKey" #keyup="getData" />
<div class="suggetions">
<ul class="suggestions" id="suggestions">
<li><h1>suggetion id</h1></li>
</ul>
</div>
</div>
</div>
</div>
You are on the right way, but, there are some issues about your logic here, for json-server you need to use the Operator _like (https://github.com/typicode/json-server#operators) to retrieve or filter data depending the column or property, so, your getData method must be like this:
async getData() {
// GET request using fetch with async/await
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?name_like=${this.searchKey}`
);
const data = await response.json();
this.result = data;
},
You can change the property or column, in example username_like or id_like.
Finally, you need to show the results, so, change your template:
<ul class="suggestions" id="suggestions">
<h1 v-for="item in result" #mousedown="show(item)">
{{ item.id }} | {{ item.name }}
</h1>
</ul>
Pay attention on #mousedown="show(item)", when user click on some result, this action will display the data about user, for that, we need to create a new method called show and pass the item:
show(item) {
alert(JSON.stringify(item, null, 2));
}
You can look how it works here: https://codepen.io/riateam/pen/ExNrGOE?editors=1010

React mapping fetched data

I'm trying to fetch some data from my database with some simple to-dos. However I cant seem to map them out into a list on my site.
I keep getting errors like: todoFromServer.map is not a function or that todoFromServer is not an array etc.
My current code looks like this:
import apiFacade from "../api/apiFacade";
import React, { useState, useEffect } from "react";
import {Form, FormGroup, Label, Input, Button} from "reactstrap"
export default function SecurePage() {
const [todoFromServer, setTodoFromServer] = useState("Waiting...");
useEffect(() => {
apiFacade.getTodo().then((data) => setTodoFromServer(data));
}, []);
return (
<div className="container-fluid padding">
<div className="row">
<div className="col-3"></div>
<div className="col-6 text-center">
<Form>
<FormGroup>
<h3 className="mt-5">Todos</h3>
<Input type="text" placeholder="Enter Todo"></Input>
</FormGroup>
<Button type="submit">Add</Button>
</Form>
<div>
{todoFromServer.map(() => (
<div>{todoFromServer.todoText}</div>
))}
</div>
</div>
</div>
</div>
);
}
The data I trying to fetch should come out as json looking like this:
I'm kind of lost.. Hope someone can help me out
to be clear - I want the data mapped out on a list with a delete button next to it...
const [todoFromServer, setTodoFromServer] = useState([]); // <=== initialize this as an empty array.
useEffect(() => {
apiFacade.getTodo().then((data) => setTodoFromServer(data)); // Make sure data returned from Promise resolve is indeed an array
}, []);
You want to read todoText of each todo's inside your array item so you would do something like this.
{todoFromServer.length ? todoFromServer.map((todo) => (
<div>{todo.todoText}</div>
)) : "Waiting..."}
For additional reference, take a look at Array.map usage here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

REACT - Image gallery

I'm retrieving images from the database in REACT and have created a holder for an image with thumbnails at the bottom.
I would like to know how I can make the interface behave like eCom sites, whereupon clicking the thumbnail, its respective image is loaded in the bigger area.
Below is the REACT code.
import React from "react";
import { Link } from "react-router-dom";
import ImageList from "../ImageList";
const ProductDetails = props => {
const images = require.context(
"../../../strapui/app/public/uploads",
true,
/\.jpg$/
);
const keys = images.keys();
const svgsArray = keys.map(key => images(key));
return(
<div className="desContainer ">
<div className="desimgContainer ">
<ImageList
styles="heroImage"
imagePath={props.selectedItem[0].image[0]}
svgsArray={svgsArray}
/>
</div>
<div className="thumbs">
<ImageList
styles="thumbnail"
imagePath={props.selectedItem[0].image[0]}
svgsArray={svgsArray}
/>
</div>
<div className="thumbs">
<ImageList
styles="thumbnail"
imagePath={props.selectedItem[0].image[1]}
svgsArray={svgsArray}
/>
</div>
<div className="thumbs">
<ImageList
styles="thumbnail"
imagePath={props.selectedItem[0].image[2]}
svgsArray={svgsArray}
/>
</div>
</div>
);
};
export default ProductDetails;
The images are pulled from the database using the following code
import React from "react";
const ImageList = props => {
if (
props.imagePath === undefined ||
props.imagePath === null ||
props.imagePath.length === 0
)
return null;
const path = props.svgsArray.find(
str => str.indexOf(props.imagePath.hash) > 1
);
return <img src={path} alt={props.imagePath.hash} className={props.styles} />;
};
export default ImageList;
I was wondering if I could use a switch case to show the image when a thumbnail is clicked?
will it work? if it will, can you pls direct me how?
Use onClick event and attach it with some function which should do some code magic.
for e.g:
largeSizeImage () {
/* some code logic */
}
return (
<div className="thumbs" onClick={largeSizeImage()}>
<ImageList
styles="thumbnail"
imagePath={props.selectedItem[0].image[1]}
svgsArray={svgsArray}
/>
</div>
)

React: Create a new html element on click

I've used React for a couple of weeks now but I have this simple problem that I can't seem to wrap my head around. It's about creating new html elements.
I would just like to know in general if the way that I went about it, is the "right way" or is there another preferred way to create new html element with a click function.
For some reason this problem took awhile for me to figure out and it still feels a bit strange, that's why I'm asking.
Thanks in advance!
import React, { Component } from 'react';
import './Overview.css';
import Project from './Project';
class Overview extends Component {
constructor() {
super()
this.state = {
itemArray: []
}
}
createProject() {
const item = this.state.itemArray;
item.push(
<div>
<h2>Title</h2>
<p>text</p>
</div>
)
this.setState({itemArray: item})
//console.log(this.state)
}
render() {
return (
<div className="Overview">
<p>Overview</p>
<button onClick={this.createProject.bind(this)}>New Project</button>
<Project />
<div>
{this.state.itemArray.map((item, index) => {
return <div className="box" key={index}>{item}</div>
})}
</div>
</div>
);
}
}
export default Overview;
No, this is not a correct approach. You shouldn't be generating HTML elements like that, nor keep them in state - it is against React to manipulate DOM like that. You won't be able to utilize Virtual DOM is the first thing that I can think of.
What you should do instead is keep all data that is needed for rendering in state and then generate the HTML element from there, for instance
createProject() {
const item = this.state.itemArray;
const title = '';
const text = '';
item.push({ title, text })
this.setState({itemArray: item})
}
render() {
return (
<div className="Overview">
<p>Overview</p>
<button onClick={this.createProject.bind(this)}>New Project</button>
<Project />
<div>
{this.state.itemArray.map((item, index) => {
return (
<div className="box" key={index}>
<div>
<h2>{item.title}</h2>
<p>{item.text}</p>
</div>
</div>
)
})}
</div>
</div>
);
}