Here is a screenshot from their docs about <Link> component
What state do they mean? A Redux state?
How does it look like to pass a state? Like this?
pathname: '/foo',
query: {
x: this.props.x,
},
state: store.getState()
It's a piece of information that you'd like to send to the next page. Nothing to do with Redux. It's a plain object. I believe Flipkart is a very nice example of how it can be used to improve user experience:
Go to a Flipkart search page on a mobile device (or simulate one using Chrome DevTools)
Tap on one of the items
You'll see that the transition happens instantly and pieces of information like product images, title, rating and price are readily available on the product page. One way to implement that is passing the state they had already loaded on the search page onto the next one:
<Link
to={`/product/${id}`}
state={{
product,
}}
/>
And then:
function ProductPage(props) {
// Always check because state is empty on first visit
if (props.location.state.product) {
console.log(props.location.state.product);
// { id: '...', images: [...], price: { ... } }
}
}
There are two ways to pass data from one route to another via Link.
URL Parameter.
As state.
URL parameter help when the route params contain strings for example we want to route to a particular profile:
<Link to='/azheraleem'>Visit Profile</Link>
However, the later i.e. the state helps us pass data from one route to another which is complex data structure. (objects/arrays).
As per the react router documentation, in case of passing data from one route to another it can be done as per the below code sample:
<Link
to={{
pathname: "/profile",
search: "?name=azheraleem",
state: { fromDashboard: true }
}}
/>
The pathname is the link to the route while the search attribute contains the query string parameters, thus the on clicking the link the URL will form something like:
http://localhost:3000/profile?name=azheraleem.
But the state variable value can be accessed in the called route using the useLocation hook:
import { useLocation } from "react-router";
const profile() => {
let data = useLocation();
console.log(data.state.fromDashboard);
}
The the state property of the to prop is the param of pushState method of History DOM object described here
That props used in push/replace methods of router as described here for transitions to a new URL, adding a new entry in the browser history like this:
router.push('/users/12')
// or with a location descriptor object
router.push({
pathname: '/users/12',
query: { modal: true },
state: { fromDashboard: true }
})
It also mentioned here:
router.push(path)
router.push({ pathname, query, state }) // new "location descriptor"
router.replace(path)
router.replace({ pathname, query, state }) // new "location descriptor"
state is a property that's part of the object you can provide to the to prop of the <Link> component.
It is particularly useful if you want to send data from the current view to one the <Link> directs you to, without using common techniques such as setting URL parameters or using libraries, such as Redux.
There isn't much official information about the state key, but here's what I found in the source code of that component:
Links may pass along location state and/or query string parameters
in the state/query props, respectively.
So basically, it's like sending props to a component from a parent. Here, you are sending "state" from the current view to the target view. That's about it, really.
In simple term state in <Link/> component is use to pass information from one view to other view through router in form of object.On other page it can be access using prop.location.state.
(Note: on browser refresh state no longer contain information)
To pass state in Link:
<Link to={{pathname: "/second_page", state: {id: 123}}} />
To access id in second page view:
let id = props.location.state.id;
For more Link properties : React Router Link
Related
I have a model:
export default Model.extend({
title: attr('string'),
attributes: attr('jsonb')
});
Where attributes is a custom json filed stored as jsonb in Postgres.
let say:
{
"name":"Bob",
"city":""
}
So I can easily manipulate attributes using template
<form.element .. #property="attributes.city"/> or model.set('attributes.city','city name')
Problem: hasDirtyAttributes do not changing because technically we have old object. But when I try to copy object let say
JSON.parse(JSON.stringify(this.get('attributes')) hasDirtyAttributes works as expected
So how to write some Mixin for a Model or other workaround which on the change of any attribute property will mark hasDirtyAttributes as true. I will update whole object so doesn't matter which property actually was changed.
Same problem: https://discuss.emberjs.com/t/hasdirtyattributes-do-not-work-with-nested-attributes-json-api/15592
existing solution doesn't work for me at all:
ember-dirtier
ember-data-relationship-tracker
ember-data-model-fragments (a lot of changes under the hood and broke my app)
Update:
Some not perfect idea that help better describe what I'm want to achieve:
Let say we adding observer to any object fileds:
export default Model.extend({
init: function(){
this._super();
this.set('_attributes', Object.assign({}, this.get('attributes'))); //copy original
Object.keys(this.get('attributes')).forEach((item) => {
this.addObserver('attributes.'+ item, this, this.objectObserver);
});
}
...
})
And observer:
objectObserver: function(model, filed){
let privateFiled = '_' + filed;
if (model.get(privateFiled) != model.get(filed)) { //compare with last state
model.set(privateFiled, this.get(filed));
model.set('attributes', Object.assign({}, this.get('attributes')) );
}
}
It's works, but when I change one filed in object due to copying object objectObserver faired again on every filed. So in this key changing every filed in object I mark observed filed as dirty
The further ember development will reduce using of event listener and two-way binding, actually Glimmer components supports only One-way Data Flow. So to be friendly with future versions of emberusing one-way data flow is good approach in this case to. So In my case as I use ember boostrap solution looks like
<form.element #controlType="textarea" #onChange={{action 'attributeChange'}}
where attributeChange action do all works.
New Glimmer / Octane style based on modifier and looks like:
{{!-- templates/components/child.hbs --}}
<button type="button" {{on "click" (fn #onClick 'Hello, moon!')}}>
Change value
</button>
Although I feel this answer is relatively close to my problem and got some reputation, I don't get it right. I read a lot of posts on how to use the "new" style of Observer-pattern ((...).pipe(map(...)).subscribe(...) and *ngFor="... | async") in Angular and now also stumbled across How to Avoid Observables in Angular. I don't want to avoid reactive behaviour; I want to have changes in the REST-API to be reflected "live" to the user without reloading the Observer. That's why I want to subscribe an object (and therefore also its properties) to an Observable (and the 'might-be-there' values from the data-stream in it), right?
In my template I have:
<p>Werte:<br><span *ngFor="let attribute of _attributes | slice:0:5; index as h">
{{attribute.name}}: <strong>{{getParamNameAt(h)}}</strong> </span><br>
<span *ngFor="let attribute of _attributes | slice:5: _attributes.length; index as h">
{{attribute.name}}: <strong>{{getParamNameAt(h + 5)}}</strong> </span></p>
In my component I have:
private _attributes: Attribute[];
constructor(private attributesService: BkGenericaAttributesService) {
attributesService.getAttributes().subscribe({ next: attributes => this._attributes = attributes });
}
getParamNameAt(h: number): string {
let attrVal = this.bkHerstArtNr.charAt(h);
return attributes[h].subModule.find(param => param.subModuleValue === attrVal).subModuleName;
}
and as service I have:
const localUrl = '../../assets/json/response.json';
#Injectable()
export class BkGenericaAttributesMockService implements BkGenericaAttributesService {
constructor(private http: HttpClient) {
}
getAttributes(): Observable<Attribute[]> {
return this.http.get<Attribute[]>(localUrl).pipe(
tap((attributes : Attribute[]) => attributes.map((attribute : Attribute) => console.log("Piping into the http-request and mapping one object after another: " + attribute.name))),
map((attributes : Attribute[]) => attributes.map((attribute : Attribute) => new Attribute(attribute.id, attribute.name, attribute.title, attribute.description,
(attribute.parameters ? attribute.parameters.map((parameter : Parameter) => new Parameter(parameter.id,
parameter.name, parameter.value)) : [])))));
}
My problem running the application at this point is the 'to-create-on-stream' Attribute-objects and "nested" Parameters[]-array (created by pushing Parameter-objects into it) pushed into the _attributes-array from the httpClient's Observable: Piping into the http-request and mapping one object after another: undefined.
Apart from this - is my construct the right way to read values from a JSON-file (or alternatively an API-stream, which may change while a user visits the SPA) into properties of multiple objects displayed on the Angular view?
With the answer mentioned above - or the way I thought I have to translate it into my code - I start to doubt that I'm really understanding (and using) the reactive Angular way with Data-Providers <= Observables => Operators => Subscribers and finally Observers displayed to the user.
I really am confused (as you can read), because a lot of answers and descriptions that I found so far use either older patterns (before Angular 5.5?) and/or partially contradict each other.
Do I handle the API-changes in the right place? Has the array for the template's *ngFor-directives to be an Observer (handled with | async) or will the changes of respectively within the array be handled by the model behind the template and the template grabs its new values (with interpolation and property binding) and also directives with a change in the components properties without asyncing?
Briefly:
Is there a for-dummies default instruction to "stream-read" values from a http-request into multiple Typescript-objects and their properties concurrently displayed in a template with on-stream-changing directives rendered only in the relevant DOM nodes, the Angular 8 opinionated way? "Stream-reading" meaning: pulling and pushing (only) if there are changes in the API, without wasting resources.
If I understand right, you want the server to push changes to the client, as soon as they happen, so that the client can react and render accordingly, right ?
this.http.get is a single call, that will resolve with a snapshot of the data. Meaning that it won't automatically update just because some data changed in your backend.
If you want to notify the client about new data or even send that data directly to the client, you'll need websockets.
There is also some problems with code:
if you .subscribe(), you'll need to .unsubscribe(), otherwise you'll end up with a memory leak.
param => param.value === attrVal, where is attrVal coming from, I don't see it being set aynwhere in the method ?
You should use the async pipe, as it unsubscribes automatically for you.
You don't neet to create class instances via new in your service, instead your Attribute typing should be an interface.
i have a gallery of images in the main view, and I want to be able to click each image to open a new child view showing details (eg text such as image title). my hardcoded version -- where images and text are stored in frontend -- works. but when I store the data in a MySQL database, i'm running into a problem -- eg unable to drill down into the details when an image is clicked.
the data seems to be fetched properly from backend to frontend because the images of the gallery are showing up. in my mind, the difference (between the hardcoded version and database-connected version) lies largely in react router v4's setup, perhaps a syntax issue that is preventing the paths from matching or preventing data from being passed to the child view:
ReactDOM.render(
..
<Route path="/articles/:PostId" render={(props) => (<Post articles={this.state.blurbs} {...props} />)} />
when it comes to errors, I get different messages depending on the browser:
one says
"TypeError: Unable to get property 'blurbs' of undefined or null reference;"
other says
"TypeError: this.state is undefined"
for brevity, my array state is initialized as such:
class App extends React.Component
..
this.state = {
blurbs: []
}
the value of the array state is updated as such:
componentDidMount()
..
let self = this;
..
return response.json();
...
.then(function(data) {
self.setState({blurbs: data});
})
in child component, data is rendered based on unique id in database as such:
render
..
return
..
{this.props.articles[this.props.match.params.PostId].Title}
so going back, what's wrong with the syntax in react router assuming everything above makes sense? tia
The issue is that the data coming from the API (eg: MySQL) is likely an array. However, you're treating it like a js object.
In your child component's render function, you need to use the findIndex method to find which element of the array that contains the value of your PostId URL parameter (docs). This is assume your table has an id element that is your primary key.
render() {
const idx = this.props.articles.findIndex(x => x.id === this.props.match.params.PostId);
const article = idx > -1 ? this.props.articles[idx] : {};
return (
...
{article.Title}
...
);
to bring "closure" to this thread, i'm sharing what worked for me. instead of passing all props from parent to child via react router, i ended up passing some props from parent to child through the image link.
the issue -- that props was not completely getting from parent to child -- was not immediately clear to me. i narrowed down my issue by tinkering around (eg substituting parts of the project with hardcode to identify the problematic parts of the code).
i found a thread where someone else was experiencing similar blockage in data transfer via react router. after several roadblocks, here are the changes that got me moving forward again.
in parent component, state reference was removed from react router to look as such:
ReactDOM.render(
..
<Route path="/articles/:PostId" render={(props) => (<Post {...props}/>)} />
in parent component, state reference was added to image link to use Link as a data transfer medium as such:
render() {
return (
<div>
{this.state.blurbs.map(image => (
<Link key={image.PostId}
to={{pathname: `/articles/${image.PostId}`,
state: { blurbs: this.state.blurbs }
}}>
<img src={image.src}/>
</Link>
))
}
</div>
in child component, this.props.location.state.blurbs was added as replacement to make the data (originally fetched in parent) accessible to child as such:
render
..
return
..
{this.props.location.state.blurbs[this.props.match.params.PostId].Title}
here's the link to the other thread:
How do i pass state through React_router?
Basically I have a SPA that is being served its content from a Rails site via JSON. Right now I have one factory that loads up a list of events with a format similar to this:
{
id: 35987,
class: "Event",
event_id: 35987,
name: "New Event",
preview: "Description of the event goes here!"
},
{
id: 35989,
class: "Event 2",
event_id: 35989,
name: "Event for Something else",
preview: "Description of the event goes here again!"
}
I then display the top 5 events on a view with a controller that takes the top 5 from the resource.
.controller('UpcomingCtrl', function($scope, upcomingFactory, localUserFactory) {
localUserFactory.get(function (user) {
upcomingFactory.query({channelID: user.channel_id}, function(data){
$scope.upcomingEvents = data;
});
});
})
This is where the tricky part for me comes in. I want the user to be able to click on one of these 5 results and then be taken to a details page for the corresponding event. The information from this detailed event comes from a separate JSON file that is labeled by the event ID using this structure http://www.testsite.com/api/v1/{{event_id}}.json
So that means I need to somehow pass the event ID from the list of events to a resource and fill in the {{event_id}} for the events .json file based on the URL the person clicks. This means the routes also have to be dynamic so that any event on http://www.testsite.com/events can become http://www.testsite.com/events/id when you click on a link.
I hope this makes sense!
Register your resource with url mappers:
$resource('testsite.com/events/:id);
You can use that url mapping by providing an object to the resource get call:
myResource.get({id: oneId}).then(...
Any other property from that object that is not defined in the resource url will go as a querystring parameter at the end of the request' url.
Edit:
I think I misunderstood your question. You can register your apps route with the same url mapping I described above. When the user clicks on an event, do:
$location.path(route + '/' + eventId);
On the detail controller, inject the $routeParams service and access your event's id as a property, for example :
$scope.eventId = $routeParams.id // or instead of id, the name of the url mapping you registered in your routes ('events/:nameOfVariable')
I'm learning both CoffeeScript and Backbone JS. I want to load just one piece of equipment. Yes, I know I don't need Backbone JS for this - But it helps me to learn if I start with basics. As soon as the page loads, I want it to grab some JSON from the server, and display it on the page.
Here is my coffeescript so far:
jQuery ->
class Equipment extends Backbone.Model
defaults:
title:''
desc:''
url:'/getData'
class ItemView extends Backbone.View
tagName: 'div'
initialize: ->
_.bindAll #, 'render'
render: ->
$(#el).html """
<h1>#{#model.get 'title'}</h2>
<p>#{#model.get 'desc'}</p>
"""
#
class AppRouter extends Backbone.Router.extend
routes:
'':'getData'
getData: ->
#equipment = new #Equipment()
#equipmentView = new #ItemView
model: #equipment
#equipment.fetch()
$('div').html #equipmentView.render().el
appRouter = new AppRouter
Backbone.history.start()
I feel like I have all the pieces in place, and am getting no errors (either in compilation or running the page).
The basic JSON I expect back from the server is just a PHP page echoing this:
{
"title": "title",
"desc": "description"
}
What am I missing?
Does #equipment.fetch() even trigger a HTTP request?
To my understanding you must set the id: #equipment = new #Equipment(id:123) which would trigger a "/getData/123" request.
or specify the url in the fetch: #equipment.fetch(url:"/getData") to load
But then the view would still be empty, because the data isn't yet loaded when the View render() is executed. Backbone doesn't automatically update views when models change (Like EmberJS does).
Add #listenTo(#model, "change", #render) to the initialize method to re-render when the model changes.
I found a nice guide/tutorial for you
http://adamjspooner.github.com/coffeescript-meet-backbonejs/
You have to tell Backbone to route your initial url ('') like this :
Backbone.history.start pushState: true
You also should pass an id (I think Backbone will request /getData/undefined in your case and on a side note I think you should use coffee's fat arrows instead of bindAll (it's one of the many great thing about coffeescript, but then you should get rid of some of the #s because they won't refer to window anymore...