I have made a small calculating website and I would like the authenticated user to save his/her calculations.
Overview:
I have a main page with 8 input fields and several labels/graphs displaying results information. Whenever you change one of the inputs, the labels and graphs update thanks to hooks.
The goal: loading data
I made a "Save" button, which, when clicked :
saves all the inputs to firebase (this works fine already)
create a link in /myaccount/dashboard, which redirects you to the main page with your saved inputs (that I need help with)
I am having a hard time finding for resources online. However, while looking for responsive calculating websites, I came across this one : https://optionstrat.com
Even-thought I have no idea what they are calculating it does what I'm looking for, ie : you can "save a trade" and then go to you account where all the saved trades are displayed.
Does anyone know a good tutorial how to do so ?
Thanks you :)
Edit
This is my save function in my App.js:
function Savecalc(){
const calcRef = db.ref("User_"+auth.currentUser.uid);
const newCalc = calcRef.push();
newCalc.set({inputs:{a,b,c},outputs:{x,y}});
/* Then attribute an URL to a saved calculation*/
}
Then, in my Dashboard.js, I would have:
const db=app.database();
export default function Dashboard() {
/* getting the user calculations */
return (
<div>
<!-- Mapping of the user's calculations -->
</div>
)
I suspect what you are probably looking for is a little library called react-router-dom. This library essentially provides a collection of navigational components that you can employ to navigate around your app. Here is a basic example. Once you install it into your project, you should create a separate AppRouter.js file that might resemble something along the lines:
import React from 'react';
// Install the react-router-dom package
import { Router, Route, Switch } from 'react-router-dom';
// Further install the history package
import { createBrowserHistory } from 'history';
// Import your dashboard component and all other components you wish to create a route to
// This is just an example
import HomePage from '../components/Homepage';
import Dashboard from '../components/Dashboard';
// You will need to create a Page Not Found component that redirects when a wrong URL is inserted
import NotFoundPage from '../components/NotFoundPage';
export const history = createBrowserHistory();
const AppRouter = () => (
<Router history={history}>
<Switch>
<Route path='/' component={HomePage} exact={true} />
<Route path='/myaccount/dashboard' component={Dashboard} />
<Route component={NotFoundPage} />
</Switch>
</Router>
);
export default AppRouter;
Then in your main/app.js file you will need to add the AppRouter component. It should look something like this:
const Application = () => (
<Provider store={store}> // if using react-redux, otherwise ignore Provider
<AppRouter />
</Provider>
);
ReactDom.render(<Application />, document.getElementById('app'));
You can also create public and private routes (e.g. accessible only for signed in users). Examples of these can be found here.
If you are saving the user's calculations, you could then query them on whatever page you want to display them on e.g. /myaccount/dashboard.
You can then map over them and display them in the UI as links, where the link could be something like /myaccount/dashboard/YlxIJ2zOxI9KYJ5Dag6t where YlxIJ2zOxI9KYJ5Dag6t is the auto-generated document ID from Firestore.
Assuming you are using React Router, you can then have a route such as:
<Route exact path="/myaccount/dashboard/:id">
On this page, you can get the ID of the document from the parameters using React Router's useParams hook like so:
const { id } = useParams();
You can then query the specific calculation's info in useEffect, and display them however you want.
Let me know if this is what you need, or if you need more help with any step!
Related
My Meteor/React application should render one static page besides the reactive one pagers with reactive UIs. The static package does not even need to be "hydrated" with the React magic after displayed in the browser. Though the server-side rendering on the server will be dynamic with React components.
I got it working, but I'm not sure if it is the intended official way to do it.
File import/client/routes.js
...
<Route path="/reactive/pages/:id" component={ReactiveComponent} />
<Route path="/static_url" />
...
File server/main.jsx
...
onPageLoad((sink) => {
if (sink.request.path === '/static_url) {
sink.renderIntoElementById('app', renderToString(
<StaticPage />,
));
}
});
...
File client/main.js
...
import { Routes } from '../imports/client/routes';
Meteor.startup(() => {
...
if (window.location.pathname !== '/offer_pdf') {
render(Routes, document.getElementById('app'));
}
});
...
Especially when rendering dependent on the URI, it seems a little bit hacky to me. Does a more elegant solution exist?
I don't think there is anything official, but in general, of course, it's a good idea to use a router for rendering different pages, so I thought it worth pointing out that you can use react-router on the server as well:
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import { StaticRouter } from 'react-router-dom';
import App from '/imports/ui/app.jsx';
onPageLoad(sink => {
const context = {};
sink.renderIntoElementById("app", renderToString(
<StaticRouter location={sink.request.url} context={context}>
<App />
</StaticRouter>
));
/* Context is written by the routes in the app. The NotFound route, used
when, uhm, no route is found, sets the status code. Here we set it on the
HTTP response to get a hard 404, not just a soft 404. Important for Google bot.
*/
context.statusCode && sink.setStatusCode(context.statusCode);
// add title to head of document if set by route
sink.appendToHead(`<title>${context.title || 'My page'}</title>`);
});
In App you can then use the usual Switch and Route tags for specifying different routes. In that you could, for instance, only specify routes that you want to be server-rendered.
I've made two components. one is Articles.js and the another one is article.js. The router is in app.js. here is code from app.js:
<Router>
<Switch>
<Route exact path="/articles" component={Articles}>
</Route>
<Route path="/article/:id" component={Article}/>
</Switch>
</Router>
I've made link to "article/:id" in Articles component. If I click on the link in articles page, it works just fine, however, If I try to reload the page or manually enter id, for eg: "article/23", it will not render anything at all on the page.
I found some results on internet, but, most of them are either not relevant or are using hooks, which I cannot use with class components.
In componentDidMount function of Article compnent i am calling getData function which fetches data from the server and then after verfiying the response sends data here to this function :
initFunction = (ar)=>{
let data = ar.map(d=>{
return(
<tr><td>{d.id}</td><td>{d.title}</td><td>{moment(d.created_on).format('MMMM,Do YYYY')}</td><td>
<Link to={`article/${d.id}`}>Edit</Link> |
<Link to={`article/delete/${d.id}`}> Delete</Link>
</td></tr>
)
})
this.setState({
tableData:<>{data}</>
})
}
And this function just generates table rows and save them in state, which I use in render function to display the data. If I go to article page through this link, it will work fine but same link will not render anything if type it manually or reload it.
Here are the components I am importing in my component:
import ReactPaginate from 'react-paginate';
import { Link } from 'react-router-dom';
import { instanceOf } from 'prop-types';
import { withCookies, Cookies } from 'react-cookie';
import moment from 'moment'
The issue here is probably that your server is not set up to serve your app on the /article route.
When you click on the link in your app then react-router does not actually request make a request to your server for that route. Instead it updates the url and re-renders your app which picks up the new route.
When you load the route directly (such as on page reload or manually typing in the url bar) the react router has not been loaded yet. Instead the browser just blindly makes a request to that route. This is when your app is failing.
I had the same issue as you. I found the solution from this thread.
react-router dynamic segments crash when accessed
added into the of my index.html
what is initState of id, I think you need to store id to state and set initState is empty string, it work for me.
const [myId, setMyId] = useState("")
I have normal and social media login options on my page, and I have created a private Route as in https://reacttraining.com/react-router/web/example/auth-workflow.
The problem is that after redirecting to for example google and back, the state of the Router history is gone.
I have created a workaround in which I store the previous URL in local storage, when the user visits the login screen.
Do you guys have any better idea on how to save History state?
I think the main issue is that you're not persisting the data.
Start off by creating a service that checks for the current user. You can persist this data in localstorage, and upon logout it should clear out the credentials.
I did an implementation like this in my previous side project.
https://github.com/EliHood/fullstacktypescript
I have something like ...
export function* getAutoLoginStatus(action) {
try {
const login = yield call(api.user.currentUser);
const token = login.token;
if (login.user.googleId !== null) {
localStorage.setItem("googleId", login.user.googleId);
}
setAuthToken(token);
sessionData.setUserLoggedIn(token);
yield put(actionTypes.getUserSuccess(login));
} catch (error) {
localStorage.clear();
yield put(actionTypes.getUserFailure(error.response.data.message));
}
}
This saga is getting called on nav component, this checks to see if user is logged in or not.
componentDidMount() {
this.props.getUser();
}
You should consider using react-router-dom, you install it by going to your project folder on command line and put:
npm i react-router-dom
Go to your App.js file and import the following:
import { BrowserRouter as Router, Route} from 'react-router-dom';
Then at your App.js file, you should render something like:
render(){
return (
<div className="App">
<Router>
<Route path="/" exact component={() => <[YourHomeJSXComponent]/>} />
<Route path="/anotherPage" exact component={()=> <[AnotherJSXComponent]/>}/>
</Router>
</div>
);
}
Then, when you want to redirect to another page, you simply put the following command:
window.location.assign("/anotherPage");
And voila, every location.assign will register an entry on your navigation history.
As an example when entering http://localhost:3000/ui/?goto=/ui/entity/e2 in the browser I'd like to go to the Entity component e2.
This is my router:
<Route path="/ui/" component={App}>
<IndexRoute component={EntitiesPage} />
<Route component={Entity} path="entity/:id" />
<Route component={NotFound} path="*" />
</Route>
This is the App component:
import React from 'react'
const App = React.createClass( {
render() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// go here: gotoUrl;
} else {
return (
<div className="App">
{this.props.children}
</div>
)
}
}
})
export default App
this.context is empty.
this.props has:
history
location
route
routeParams (empty)
routes
UPDATE:
I've ended up using this:
import React from 'react'
import { withRouter } from 'react-router'
const App = React.createClass( {
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
this.props.router.replace(gotoUrl);
}
},
render() {
return (
<div className="App">
{this.props.children}
</div>
);
}
})
export default withRouter(App)
One thing that might be tripping you up is that render should have no side effects.
A "side effect" is anything that changes what's going on in your app*: updating state, making AJAX calls, or in this case, altering the page location. The render method should only read from the current state of the component, then return a value.
Because you're already using React.createClass, the best way to handle this is by adding a separate method that React handles specially: componentWillMount. I'd recommend you put your "redirect" logic here.
In order to properly change the page location, you'll need access to the browser history object which react-router manipulates. You can import this from the react-router library itself and directly call methods on it:
// At top of file
import { browserHistory } from 'react-router'
// Then, in your component:
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// NOTE: this may have security implications; see below
browserHistory.push(gotoUrl);
}
}
Source: documentation.
I'd suggest that, instead of using query.goto, you instead select a parameter that can be easily validated, such as the entity ID itself (a simple regex can make sure it's valid). Otherwise, an unscrupulous user might send a link to another user and cause them to access a page that they didn't mean to.
*Note: there are stricter definitions of "side effect" out there, but this one is pretty useful for React development.
You should use browserHistory
import { browserHistory } from 'react-router';
...
if (gotoUrl) {
browserHistory.push(gotoUrl)
}
Let me know if this works
I am not sure how to get clean url with react router.
For the moment I have this:
http://localhost:8889/#/myRoute?_k=qq67x0
I would like to have this:
http://localhost:8889/myRoute
Is there a particular configuration step that I should set to fix this?
This is how I initialize the router:
import { browserHistory, Router, Route, Link, IndexRoute } from 'react-router
And here is my render function:
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={MyComponent2} />
<Route path="myComponent1" component={MyComponent1} />
<Route path="myComponent2" component={MyComponent2} />
</Route>
</Router>
), document.getElementById('react-container'))
EDIT:
I have installed the last version of the router and now it works as expected.
Thanks!
Take a look at the documentation under "What is that ?_k=ckuvup junk in the URL?":
When a history transitions around your app with push or replace, it can store "location state" that doesn't show up in the URL on the new location, think of it a little bit like post data in an HTML form.
The DOM API that hash history uses to transition around is simply window.location.hash = newHash, with no place to store location state. But, we want all histories to be able to use location state, so we shim it by creating a unique key for each location and then store that state in session storage. When the visitor clicks "back" and "forward" we now have a mechanism to restore the location state.
The history package docs explain how to opt out, if you want to continue to use the hash history:
If you prefer to use a different query parameter, or to opt-out of this behavior entirely, use the queryKey configuration option.
import createHistory from 'history/lib/createHashHistory'
// Use _key instead of _k.
let history = createHistory({
queryKey: '_key'
})
// Opt-out of persistent state, not recommended.
let history = createHistory({
queryKey: false
})
If you want to use the HTML 5 pushState API, as you mentioned in your question, then you should use browserHistory in your Router configuration instead of hashHistory:
import { Router, browserHistory } from 'react-router'
<Router history={browserHistory}>
...
She the full "Histories" page in the React Router docs for more information.