how to use react joyride in multiple pages - reactjs

Is there a way of creating a react-joyride tour in multiple pages.so my index.js file looks like below? I added react-joyride in index page because all components run through the index.js file.
class IndexApp extends Component {
constructor(props) {
super(props);
this.state = {
view: false,
run: true,
steps: [
{
target: '.cc',
content: 'This is my awesome feature!',
},
],
stepIndex: 0
};
}
handleJoyrideCallback = data => {
const { action, index, status, type } = data;
console.log("ghgg")
if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
// Update state to advance the tour
this.setState({ stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) });
}
else if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) {
// Need to set our running state to false, so we can restart if we click start again.
this.setState({ run: false });
}
console.groupCollapsed(type);
console.log(data); //eslint-disable-line no-console
console.groupEnd();
};
componentDidCatch(error, errorInfo) {
this.setState({ error });
unregister();
}
render() {
const { view,run, stepIndex, steps } = this.state;
if (view) {
return( <div>
{this.props.children}
<Joyride
callback={this.handleJoyrideCallback}
run={run}
stepIndex={stepIndex}
steps={steps}
/>
</div>);
} else {
return null;
}
}
}

You can use globalState for this, then create a hook on all pages.
For global state : https://endertech.com/blog/using-reacts-context-api-for-global-state-management
https://github.com/gilbarbara/react-joyride/discussions/756
const initialState = {
tour: {
run: false,
steps: []
}
}

Related

React Lifecycle- The plot is drawn using the initial state value, and not using the fetched value

I want to fetch the value of the variable 'r2score' from flask. The value is fetched successfully. I even wote a console.log(r2score) statement to see if the fetching works. Here's the problem. Initially it logged a value of 0.1, which is its initial state. then in the next line of the console it logged a value of 0.26, which the value that was fetched from flask. So atleast the fetching was successful. However, the plot that is being drawn, is drawn with a value of 0.1(it's initial state) and not 0.26(it's fetched value).
My Code:
import ReactDOM from "react-dom";
import React from "react";
import { Liquid } from "#antv/g2plot";
import ReactG2Plot from "react-g2plot";
class R2ScorePlot extends React.Component {
constructor(props) {
super(props);
this.state = { r2score: 0.1 };
}
componentDidMount() {
fetch(`/fetch_regressionmodel`)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong ...");
}
})
.then(info =>
this.setState({
r2score: info.R2score
})
).then( this.forceUpdate() )
.catch(error => this.setState({ error }));
}
shouldComponentUpdate() {
return true;
}
render() {
const { r2score } = this.state;
console.log(r2score);
const config = {
title: {
visible: false,
text: ""
},
description: {
visible: false,
text: ""
},
min: 0,
max: 1,
value: r2score,
statistic: {
formatter: value => ((1 * value) / 1).toFixed(1)
}
};
return (
<div>
<ReactG2Plot Ctor={Liquid} config={config} />
</div>
);
}
}
export default R2ScorePlot;
Console Image
React Dev Tools
Have solved the issue. The solution was to wrap the graph component in a
<div key={r2score}>...</div>
So that the graph will rebuild whenever, the key changes.
My code:
import ReactDOM from "react-dom";
import React from "react";
import { Liquid } from "#antv/g2plot";
import ReactG2Plot from "react-g2plot";
class R2ScorePlot extends React.Component {
constructor(props) {
super(props);
this.state = { r2score: 0.1 };
}
componentDidMount() {
fetch(`/fetch_regressionmodel`)
.then(response => {
if (response.ok) {
this.setState({ spinloading: false });
return response.json();
} else {
throw new Error("Something went wrong ...");
}
})
.then(info =>
this.setState({
r2score: info.Explained_Variance_score
})
).catch(error => this.setState({ error }));
}
shouldComponentUpdate() {
return true;
}
render() {
const { r2score } = this.state;
console.log(r2score);
const config = {
title: {
visible: false,
text: ""
},
description: {
visible: false,
text: ""
},
min: 0,
max: 1,
value: r2score,
statistic: {
formatter: value => ((1 * value) / 1).toFixed(1)
}
};
return (
<div key={r2score}>
<ReactG2Plot Ctor={Liquid} config={config} />
</div>
);
}
}
export default R2ScorePlot;

React App Rendering Before Firestore Data Has Loaded

I am trying to load data from Firestore and show it in the gantt-chart, but it renders before it has loaded the data from firebase. So I call setState inside of componentDidMount because I thought this would then call the render again at which point the data would be there. But it is still sitting empty. Any ideas as to why?
import React, { Component } from 'react';
import Gantt from './Gantt';
import Toolbar from './Toolbar';
import MessageArea from './MessageArea';
import Firebase from './Firebase';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentZoom: 'Days',
messages: [],
projects: [],
links: []
};
this.handleZoomChange = this.handleZoomChange.bind(this);
this.logTaskUpdate = this.logTaskUpdate.bind(this);
this.logLinkUpdate = this.logLinkUpdate.bind(this);
}
componentDidMount() {
const db = Firebase.firestore();
var projectsArr = [];
db.collection('projects').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
let project = doc.data();
projectsArr.push({id: 1, text: project.name, start_date: '15-04-2017', duration: 3, progress: 0.6});
});
});
this.setState({
projects: projectsArr
});
}
addMessage(message) {
var messages = this.state.messages.slice();
var prevKey = messages.length ? messages[0].key: 0;
messages.unshift({key: prevKey + 1, message});
if(messages.length > 40){
messages.pop();
}
this.setState({messages});
}
logTaskUpdate(id, mode, task) {
let text = task && task.text ? ` (${task.text})`: '';
let message = `Task ${mode}: ${id} ${text}`;
this.addMessage(message);
}
logLinkUpdate(id, mode, link) {
let message = `Link ${mode}: ${id}`;
if (link) {
message += ` ( source: ${link.source}, target: ${link.target} )`;
}
this.addMessage(message)
}
handleZoomChange(zoom) {
this.setState({
currentZoom: zoom
});
}
render() {
var projectData = {data: this.state.projects, links: this.state.links};
return (
<div>
<Toolbar
zoom={this.state.currentZoom}
onZoomChange={this.handleZoomChange}
/>
<div className="gantt-container">
<Gantt
tasks={projectData}
zoom={this.state.currentZoom}
onTaskUpdated={this.logTaskUpdate}
onLinkUpdated={this.logLinkUpdate}
/>
</div>
<MessageArea
messages={this.state.messages}
/>
</div>
);
}
}
export default App;
You are calling setState outside of the then callback.
So Change
db.collection('projects').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
let project = doc.data();
projectsArr.push({id: 1, text: project.name, start_date: '15-04-2017', duration: 3, progress: 0.6});
});
});
this.setState({
projects: projectsArr
});
To
db.collection('projects').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
let project = doc.data();
projectsArr.push({id: 1, text: project.name, start_date: '15-04-2017', duration: 3, progress: 0.6});
});
this.setState({
projects: projectsArr
});
});
Also, as a general pattern you can do something like this:
class AsyncLoad extends React.Component {
state = { data: null }
componentDidMount () {
setTimeout(() => {
this.setState({ data: [1, 2, 3]})
}, 3000)
}
render () {
const { data } = this.state
if (!data) { return <div>Loading...</div> }
return (
<pre>{JSON.stringify(data, null, 4)}</pre>
)
}
}
It's a common enough operation to create an HOC for it.

Lot of repetition in React component

I have a rather large React component that manages the display of a detail for a job on my site.
There are a few things that I would like to do smarter
The component has a few options for opening Dialogs. For each dialog I have a separate Open and Close function. For example handleImageGridShow and handleImageGridClose. Is there any way to be more concise around this?
I have many presentational components (e.g. ViewJobDetails) that shows details about the job. My issue is that I have to pass them down into each Component as a prop and I'm passing the same props over and over again
As I'm loading my data from firebase I often have to do similar checks to see if the data exists before I render the component (e.g.this.state.selectedImageGrid && <ImageGridDialog />). Is there any more clever way of going about this?
import React, { Component } from 'react';
import { withStyles } from 'material-ui/styles';
import ViewJobAttachment from "../../components/jobs/viewJobAttachment";
import ViewJobDetails from "../../components/jobs/viewJob/viewJobDetails";
import ViewJobActions from "../../components/jobs/viewJob/viewJobActions";
import ViewCompanyDetails from "../../components/jobs/viewJob/viewCompanyDetails";
import ViewClientsDetails from "../../components/jobs/viewJob/viewClientsDetails";
import ViewProductsDetails from "../../components/jobs/viewJob/viewProductsDetails";
import ViewAttachmentDetails from "../../components/jobs/viewJob/viewAttachmentDetails";
import ViewEventLogDetails from "../../components/jobs/viewJob/viewEventLogDetails";
import ViewSummaryDetails from "../../components/jobs/viewJob/viewSummary";
import {FirebaseList} from "../../utils/firebase/firebaseList";
import SimpleSnackbar from "../../components/shared/snackbar";
import {calculateTotalPerProduct} from "../../utils/jobsService";
import BasicDialog from "../../components/shared/dialog";
import ImageGrid from "../../components/shared/imageGrid";
import Spinner from "../../components/shared/spinner";
import ViewPinnedImageDialog from "../../components/jobs/viewEntry/viewPinnedImage";
import {
Redirect
} from 'react-router-dom';
const styles = theme => ({
wrapper: {
marginBottom: theme.spacing.unit*2
},
rightElement: {
float: 'right'
}
});
const ImageGridDialog = (props) => {
return (
<BasicDialog open={!!props.selectedImageGrid}
handleRequestClose={props.handleRequestClose}
fullScreen={props.fullScreen}
title={props.title}
>
<ImageGrid selectedUploads={props.selectedImageGrid}
handleClickOpen={props.handleClickOpen}/>
</BasicDialog>
)
};
class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: null,
entries: [],
promiseResolved: false,
attachmentDialogOpen: false,
openAttachment: null,
selectedImageGrid: false,
selectedPinnedImage: false,
showSnackbar: false,
snackbarMsg: '',
markedImageLoaded: false,
loading: true,
redirect: false
};
this.firebase = new FirebaseList('jobs');
this.handleJobStatusChange = this.handleJobStatusChange.bind(this);
this.handleImageGridShow = this.handleImageGridShow.bind(this);
this.handleImageGridClose = this.handleImageGridClose.bind(this);
this.handlePinnedImageClose = this.handlePinnedImageClose.bind(this);
this.handlePinnedImageShow = this.handlePinnedImageShow.bind(this);
this.handleMarkedImageLoaded = this.handleMarkedImageLoaded.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.pushLiveToClient = this.pushLiveToClient.bind(this);
}
componentDidMount() {
this.firebase.db().ref(`jobs/${this.props.id}`).on('value', (snap) => {
const job = {
id: snap.key,
...snap.val()
};
this.setState({
currentJob: job,
loading: false
})
});
const previousEntries = this.state.entries;
this.firebase.db().ref(`entries/${this.props.id}`).on('child_added', snap => {
previousEntries.push({
id: snap.key,
...snap.val()
});
this.setState({
entries: previousEntries
})
});
}
handleRemove() {
this.firebase.remove(this.props.id)
.then(() => {
this.setState({redirect: true})
})
};
pushLiveToClient() {
const updatedJob = {
...this.state.currentJob,
'lastPushedToClient': Date.now()
};
this.firebase.update(this.state.currentJob.id, updatedJob)
.then(() => this.handleSnackbarShow("Job pushed live to client"))
}
handleJobStatusChange() {
const newState = !this.state.currentJob.completed;
const updatedJob = {
...this.state.currentJob,
'completed': newState
};
this.firebase.update(this.state.currentJob.id, updatedJob)
}
handleSnackbarShow = (msg) => {
this.setState({
showSnackbar: true,
snackbarMsg: msg
});
};
handleSnackbarClose= (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ showSnackbar: false });
};
handleAttachmentDialogClose =() => {
this.setState({attachmentDialogOpen: false})
};
handleClickOpen = (file) => {
this.setState({
attachmentDialogOpen: true,
openAttachment: file
});
};
handleImageGridShow(imageGrid) {
this.setState({selectedImageGrid: imageGrid})
}
handleImageGridClose() {
this.setState({selectedImageGrid: false})
}
handlePinnedImageShow(pinnedImage) {
this.setState({selectedPinnedImage: pinnedImage})
}
handlePinnedImageClose() {
this.setState({selectedPinnedImage: false})
}
handleMarkedImageLoaded() {
this.setState({markedImageLoaded: true})
}
render() {
const {classes} = this.props;
let {_, costPerItem} = calculateTotalPerProduct(this.state.entries);
if (this.state.redirect) {
return <Redirect to='/jobs' push/>
} else {
if (this.state.loading) {
return <Spinner/>
} else {
return (
<div className={styles.wrapper}>
{this.state.currentJob &&
<div>
<ViewJobActions currentJob={this.state.currentJob}
handleJobStatusChange={this.handleJobStatusChange}
pushLiveToClient={this.pushLiveToClient}
/>
<ViewJobDetails currentJob={this.state.currentJob}/>
<ViewCompanyDetails currentJob={this.state.currentJob}/>
<ViewClientsDetails currentJob={this.state.currentJob}/>
<ViewProductsDetails currentJob={this.state.currentJob}/>
{this.state.currentJob.selectedUploads && this.state.currentJob.selectedUploads.length > 0
? <ViewAttachmentDetails currentJob={this.state.currentJob} handleClickOpen={this.handleClickOpen}/>
: null}
<ViewEventLogDetails jobId={this.state.currentJob.jobId}
jobKey={this.state.currentJob.id}
entries={this.state.entries}
handlePinnedImageShow={this.handlePinnedImageShow}
handleImageGridShow={this.handleImageGridShow}/>
<ViewSummaryDetails stats={costPerItem}/>
<ViewJobAttachment open={this.state.attachmentDialogOpen}
handleRequestClose={this.handleAttachmentDialogClose}
attachment={this.state.openAttachment}
/>
{this.state.selectedImageGrid &&
<ImageGridDialog selectedImageGrid={this.state.selectedImageGrid}
handleRequestClose={this.handleImageGridClose}
handleClickOpen={this.handleClickOpen}
title="Pictures for job"
fullScreen={false}/>}
{this.state.selectedPinnedImage &&
<ViewPinnedImageDialog attachment={this.state.selectedPinnedImage}
open={!!this.state.selectedPinnedImage}
markedImageLoaded={this.state.markedImageLoaded}
handleMarkedImageLoaded={this.handleMarkedImageLoaded}
handleRequestClose={this.handlePinnedImageClose}
otherMarkedEntries={this.state.entries}
/>
}
<SimpleSnackbar showSnackbar={this.state.showSnackbar}
handleSnackbarClose={this.handleSnackbarClose}
snackbarMsg={this.state.snackbarMsg}/>
</div>}
</div>
);
}
}
}
}
export default withStyles(styles)(ViewJob);
You can define a regular component method and bind it in handler like this onSomething={this.handler.bind(this, index)} assuming you have some distinguishable thing in the index var
function should look like this
handler(index) {
...
}

React setState of array of objects

I have an array of 10 objects (Lets call them "Blogs") which contain title, description and image-URL properties. I need to wrap each of the properties in HTML tags and export them all so they all load on a webpage together.
With my current code, I am only getting 1 of the objects in the current state loading on the page. How do I get all the objects in the same state?
class NewBlogs extends React.Component {
constructor(props) {
this.state = {
title: [],
description: [],
image: [],
loading: true
};
}
componentDidMount() {
axios.get('/new-blogs').then(data => {
const blogs = data.data;
var component = this;
for(var i in blogs) {
component.setState({
title: blogs[i].title,
description: blogs[i].description,
image: blogs[i].image,
loading: false
});
}
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div>
<h2>New Blogs:</h2>
<h3>{this.state.title}</h3>
<em>{this.state.description}</em>
<img src={this.state.image}></img>
</div>
);
}
}
export default NewBlogs
I haven't run/test this but try something like this
The API call appears to return a list of objects. If so just set state once the xhr completes and set loading false once.
In the react render() is where you could iterate over your list. The easiest way to do that is with '.map()'. You then simply return react elements for each object in your list.
Also let's rename 'component' to 'list'
class NewBlogs extends React.Component {
constructor(props) {
this.state = {
list: [],
loading: true
};
}
componentDidMount() {
axios.get('/new-blogs').then(data => {
// const blogs = data.data;
// var component = this;
this.setState({list: data.data, loading: false })
// for(var i in blogs) {
// this.setState({
// title: blogs[i].title,
// description: blogs[i].description,
// image: blogs[i].image,
// loading: false
// });
// }
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div>
{this.state.list.map(e => (
<h2>New Blogs:</h2>
<h3>{e.title}</h3>
<em>{e.description}</em>
<img src={e.image}></img>
))}
</div>
);
}
}
export default NewBlogs

React- Elegantly Toggle State Visibility

I'm currently trying to integrate my own column selector into the flask-bootstrap-table 3.0-beta2 package. I found an example on the github in the issues section which is as follows:
export default class ColumnHideTable extends React.Component {
constructor(props) {
super(props);
this.state = { showModal: false, hiddenColumns: {} };
}
changeColumn(id) {
return () => {
this.setState({ hiddenColumns: Object.assign(this.state.hiddenColumns, { id: !this.state.hiddenColumns.id }) });
};
}
}
However, this will only show/hide the ID column unsurprisingly and the checkboxes for the other values are stuck with checked values and when clicked, only check/uncheck and hide/show the id column
I'm trying to work out a solution using computed variables and I've cooked up the following:
setColumnState(column) {
let columns = Object.keys(cyhyData[0])
var o = {}
for(let i=0; i < columns.length;i++) {
if(column == columns[i]) {
Object.defineProperty(o, column, {
value: !this.state.hiddenColumns.column,
enumerable: true
})
break
}
}
return o
}
changeColumn(column) {
return () => {
this.setState({ hiddenColumns: Object.assign(this.state.hiddenColumns, this.setColumnState(column)) });
console.log(this.state.hiddenColumns)
};
}
This correctly hides the columns, but obviously wont un-hide them. How can I toggle !this.state.hiddenColumns.givenCol?
Is there perhaps a cleaner solution I'm not seeing?
I hacked together a solution for anyone who needs it. It's not elegant, but it works :) any suggestions to make this cleaner are welcome!
constructor(props) {
super(props);
this.state = { showModal: false, hiddenColumns: {} };
}
changeColumn(column) {
return () => {
var o = {}
if(!this.state.hiddenColumns.hasOwnProperty(column)) {
Object.defineProperty(o, column, {
value: true,
enumerable: true
});
this.setState({ hiddenColumns: Object.assign(this.state.hiddenColumns, o) });
} else {
this.setState({ hiddenColumns: Object.assign(!this.state.hiddenColumns, o) });
};
}
}
closeColumnDialog = (onClick) => {
this.setState({ showModal: false });
}
openColumnDialog = (onClick) => {
this.setState({ showModal: true });
}

Resources