Need to render a Custom React component array as Virtual Slides using Swiper.
Document says renderExternal can be used for this but there's no example in the API doc http://idangero.us/swiper/api/#virtual
Need to know on how this can be done using renderExternal function.
I'm not sure that's the best way to do it, but that is roughly how I did :
const mySwiper = new Swiper('.swiper-container', {
virtual: {
slides: this.props.array (or this.state.array)
renderExternal: function(data){}
}
})
then you display your component in the template :
<div className="swiper-container">
<div className="swiper-wrapper">
<MyComponent
myProps="mySwiper.virtual.slides[mySwiper.activeIndex]"
/>
</div>
</div>
(I did it with Vue, not React)
The documentation got updated and now you can find an example of how to use it with React. For reference, here is the example that is given:
import React from 'react';
import Swiper from 'swiper/dist/js/swiper.esm.bundle';
export default class extends React.Component {
constructor() {
this.state = {
// dummy slides data
slides: (function () {
var slides = [];
for (var i = 0; i < 600; i += 1) {
slides.push('Slide ' + (i + 1));
}
return slides;
}()),
// virtual data
virtualData: {
slides: [],
},
}
}
componentDidMount() {
const self = this;
const swiper = new Swiper('.swiper-container', {
// ...
virtual: {
slides: self.state.slides,
renderExternal(data) {
// assign virtual slides data
self.setState({
virtualData: data,
});
}
},
});
}
render() {
{/* ... */}
<div className="swiper-container">
<div className="swiper-wrapper">
{/* It is important to set "left" style prop on every slide */}
{this.state.virtualData.slides.map((slide, index) => (
<div className="swiper-slide"
key={index}
style={{left: `${virtualData.offset}px`}}
>{slide}</div>
))}
</div>
</div>
{/* ... */}
}
}
Related
I'm working on a school project. With Reactjs. I have a side panel that includes a drop down box that gives 2 options
"live" -- display the live graph
"processed" -- show another graph created by importing a certain JSON file.
Here is the SidePanel.js I had. I do have a Graph.js for the graph, and a GraphContainer.js that basically put the SidePanel and Graph together.
I'm new to React so my code isn't good and sloppy.
If this question is a duplicate, please kindly let me know.
I'm also not a frequent on Stackoverflow, so if there's something I can do to improve my post, please let me know!
Thank you for your time!
import React, { Component } from 'react';
import './SidePanel.css'
class SidePanel extends Component {
constructor (props){
super(props)
this.state = {}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return(
<div className="SideContainer">
<div className="upSide">
<h4>Information</h4>
<ul>
<li>Device: <b>Device 1</b></li>
<li>Status: <b>Connected</b></li>
</ul>
</div>
<div className="downSide">
<form>
<label>
<h4>Options</h4>
<select value={this.state.value} onChange={this.handleChange}>
<option value="live">Live</option>
<option value="processed">Processed</option>
</select>
</label>
</form>
</div>
</div>
);
}
}
export default SidePanel;
So what I'm doing is I'm putting a GraphContainer.js to contain the Graph and the SidePanel, so the GraphContainer.js should not matter that much.
This is my Graph.js:
import React, { Component } from 'react';
import Highcharts from 'highcharts';
import socketIOClient from "socket.io-client";
import {
HighchartsChart, Chart, withHighcharts, XAxis, YAxis, Title, Legend, LineSeries
} from 'react-jsx-highcharts';
import './Graph.css'
var jsonData = import '../jsonData.json'
//pass the json file location
class Graph extends Component {
constructor (props) {
super(props);
this.addDataPoint = this.addDataPoint.bind(this);
const now = Date.now();
this.state = {
data: []
}
}
componentDidMount() {
//GRAPH 1: Here is to somehow put JSONdata
console.log(jsonData)
//GRAPH 2: Here is taking data from socket for testing
var sensorSerial = this.props.match.params.sensorId
const socket = socketIOClient("http://localhost:5001/test");
socket.on("newnumber", data => this.addDataPoint(data));
}
addDataPoint(data){
if(data.sensorSerial == this.props.match.params.sensorId){
var newData = this.state.data.slice(0)
console.log(new Date(data.dateTime.split(' ').join('T')))
newData.push([new Date(data.dateTime.split(' ').join('T')), data.number])
this.setState({
data: newData
})
}
}
render() {
// const {data} = this.state;
// console.log(new Date("2019-04-04T10:55:08.841287" + Z))
const plotOptions = {
series: {
pointStart: new Date("2019-04-04T10:55:08.841287Z")
}
}
return (
<div className="graph">
<HighchartsChart plotOptions={plotOptions}>
<Chart />
<Title>Data</Title>
<Legend layout="vertical" align="right" verticalAlign="middle" >
<Legend.Title>Legend</Legend.Title>
</Legend>
<XAxis type="datetime">
<XAxis.Title>Time</XAxis.Title>
</XAxis>
<YAxis>
<YAxis.Title>Y-axis</YAxis.Title>
<LineSeries name="Channel 1" data={this.state.data}/>
</YAxis>
</HighchartsChart>
</div>
);
}
}
export default withHighcharts(Graph, Highcharts);
And here is the example JSON file I want to test:
{
"data": [
{"datetime": "", "data": [1,2,3,4]},
{"datetime": "", "data": [1,2,3,4]}
]
}
You can use conditional rendering like this (Note // are comments ).
render(){
let {value } = this.state // we are using destructuring here to get the value
// property - if are you using babel , so you can
// use ES6 syntax for your Javascript code ? If not
// just use this.state.value
// from state or you could just use this.state.value
let data = value !== 'live ' ? this.getJSONdata() : this.getLiveData()
// Call functions to get the data depending on the value - see below
return (
// wherever in your jsx you want to display your graph
<Graph data={data} />
)
}
So above the render function you'd want to have the two functions you want to use getJSONdata and getLiveData
They would have the syntax below (again Im using arrow functions (ES6 syntax))
getJSONData = () => // get your data
// or not es6 sytax
getJSONData = function() {}
If you let me know how you are going to get your data I'll help you with how best to get it.
I had a functional React Component that looked like this:
import React from 'react';
const expand = (itemId) => {
alert('item ' + itemId + ' clicked!');
}
const Item = ({itemData: {id, title, thumbnail}}) => {
let hasThumb = !(thumbnail === 'self' || thumbnail === 'nsfw');
return (
<div className="item" onClick={() => expand(id)}>
<h3 className="item-title">{title}</h3>
<img className="item-thumbnail" src={hasThumb ? thumbnail: 'placeholder.png'}
alt={hasThumb ? 'Item thumbail' : 'Placeholder thumbnail'} />
</div>
);
};
export default Item;
It works perfectly! But I want to add functionality that will require me to make it stateful/change it to a class-based Component structure. I did it like this:
import React, {Component} from 'react';
class Item extends Component {
expand = (itemId) => {
alert('item ' + itemId + ' clicked!');
}
render() {
let hasThumb = !(this.props.itemData.thumbnail === 'self' || this.props.itemData.thumbnail === 'nsfw');
return (
<div className="item" onClick={() => this.expand(this.props.itemData.id)}>
<h3 className="item-title">{this.props.itemData.title}</h3>
<img className="item-thumbnail" src={hasThumb ? this.props.itemData.thumbnail: 'placeholder.png'}
alt={hasThumb ? 'Item thumbail' : 'Placeholder thumbnail'} />
</div>
);
};
}
export default Item;
It compiles perfectly with no errors, but in the browser I get TypeError: _ref is undefined and it calls out the line number for the render definition. I found that removing the destructuring statement in the render argument makes this go away (I can refer to everything as this.props.data.[whatever]) but this is inconvenient compared with being able to destructure, as I'm able to do in a functional React component. What have I done wrong here? Or is destructuring in this way simply not possible?
In React, for class based component destructuring is done inside the method.
eg:
render() {
let { itemData: {id, title, thumbnail} } = this.props;
.....
}
componentDidMount() {
let { itemData: {id, title, thumbnail} } = this.props;
.....
}
I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;
You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate
I built a list using a json object loaded into the state.
The li items seem to be inaccessible from the document.ready function. i.e. binding a click function to the li's doesn't work.
However, I can bind to the parent UL which I find odd.
Here is my list component:
import React from 'react';
var Albums = React.createClass({
render() {
return (
<ul id="frames_list">
{this.props.frames.map(function(frames){
return (
<li key={frames.frameId}>
<Link to="/frames" >{frames.frameName}</Link>
</li>
)
}.bind(this))}
</ul>
)
}
});
module.exports = Albums;
Here is my doc.ready code:
$('#frames_list li').click(function() {
alert('hello');
});
Here is the parent component setting state:
import React from 'react';
import SideNavHeader from './SideNavHeader';
import SideNavAccordionMenu from './SideNavAccordionMenu';
import SideNavFooter from './SideNavFooter';
var framesData, albumsData, channelsData;
$.getJSON("/json/frames.json", function(frames){
framesData = frames;
});
$.getJSON("/json/albums.json", function(albums){
albumsData = albums;
});
$.getJSON("/json/channels.json", function(channels){
channelsData = channels;
});
var SideNav = React.createClass({
getInitialState() {
return {
frames: [],
albums: [],
channels: []
};
},
componentDidMount() {
var framesList = [], albumsList = [], channelsList = [];
// Fake an AJAX request with a setTimeout function
setTimeout(function(){
framesList=framesData.frames;
albumsList=albumsData.albums;
channelsList=channelsData.channels;
// Set state when the request completes
this.setState({
frames: framesList,
albums: albumsList,
channels: channelsList
});
}.bind(this), 10);
},
render() {
return (
<section>
<SideNavHeader />
<SideNavAccordionMenu frames={this.state.frames} albums={this.state.albums} channels={this.state.channels} />
<SideNavFooter />
</section>
);
}
});
module.exports = SideNav;
You don't want to be attaching click listeners out in your DOM. React is going to be doing stuff to your DOM, including destroying nodes and creating new ones. This may break your listeners, which are tied to specific nodes, which might go away.
In this case, you probably want to use React's built in onClick attribute in JSX, as seen in the docs.
Try something like:
import React from 'react';
var Albums = React.createClass({
handleClick(event) {
console.log('li was clicked');
},
render() {
return (
<ul id="frames_list">
{this.props.frames.map(function(frames){
return (
<li key={frames.frameId} onClick={this.handleClick}>
<Link to="/frames" >{frames.frameName}</Link>
</li>
)
}.bind(this))}
</ul>
)
}
});
module.exports = Albums;
As an aside, for clarity, I personally like break out the mapping through items into its own function:
import React from 'react';
var Albums = React.createClass({
handleClick(event) {
console.log('li was clicked');
},
renderFrames() {
return this.props.frames.map(frame => {
return (
<li key={frame.frameId} onClick={this.handleClick}>
<Link to="/frames" >{frame.frameName}</Link>
</li>
);
});
},
render() {
return (
<ul id="frames_list">
{this.renderFrames()}
</ul>
)
}
});
module.exports = Albums;
if you really want to use jquery event listener, you have to bind your click event in ComponentDidMount function (in your Albums component)
I am using this react-sidenav (https://github.com/wmira/react-sidenav) to create a side nav with multiple options.
This is my code using the nav:
nav.jsx
import React from 'react';
import SideNav from "react-sidenav";
var TeamActions = require('../actions/TeamActions');
export default class Nav extends React.Component {
pushName (name) {
TeamActions.setTeamName(name);
}
render() {
return( <SideNav.Menu path="#" itemHeight="32px" styles={{margin: "0"}} onClick={this.pushName.bind(null, key)}>
<SideNav.MenuItem itemKey="Boston Celtics" >
<SideNav.ILeftItem className="fa fa-truck" >
Boston Celtics
</SideNav.ILeftItem>
</SideNav.MenuItem>
<SideNav.MenuItem itemKey="bed">
<SideNav.ILeftItem className="fa fa-bed">
Dallas Mavericks
</SideNav.ILeftItem>
</SideNav.MenuItem>
</SideNav.Menu>
)
}
}
How can I use the onClick method to send the name (e.g "Boston Celtics") of the item being pressed? Currently onClick can be used in the place it is now as far as I've tried.
Added the react/sidenav.js file: https://github.com/wmira/react-sidenav/blob/master/js/react-sidenav.js
Edit: So I switched to (https://github.com/balloob/react-sidebar)'s sidebar which was more conducive to adding a onClick function through Jim's method.
it seems like you're using ul, li, a to build your content. here's the direction to help you with
var items = ['banana', 'applea'];
var List = React.createClass({
_handleClick: function(e) {
alert(e.target.id);
},
_renderItems: function() {
var content = items.map(function(item) {
return (
<li key={item} id={item} onClick={this._handleClick}>{item}</li>
);
}.bind(this));
return content;
},
render: function() {
var content = this._renderItems();
return (
<ul>
{content}
</ul>
);
}
});
React.render(
<List />,
document.getElementById('app')
);
here's the jsbin link: http://jsbin.com/foxoxetana/edit?js,console,output