How do properly use Onsen's Navigator in React? - reactjs

I'm trying to implement a simple Onsen Navigator in React.
So far I'm receiving an error 'route is not defined' and I was looking through the examples & docs but I only saw the initialRoute prop was provided, how & where does the route prop generated or something? Cause it seems like its not specified.
Here is my the code of my component:
import React, {PropTypes} from 'react';
import ons from 'onsenui';
import * as Ons from 'react-onsenui';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
index : 0
};
this.renderPage = this.renderPage.bind(this);
this.pushPage = this.pushPage.bind(this);
}
pushPage(navigator) {
navigator.pushPage({
title: `Another page ${this.state.index}`,
hasBackButton: true
});
this.setState({index: this.state.index++});
}
renderPage(route, navigator) {
return (
<Ons.Page key={route.title}>
<section style={{margin: '16px', textAlign: 'center'}}>
<Ons.Button onClick={this.pushPage}>
Push Page
</Ons.Button>
</section>
</Ons.Page>
);
}
render() {
return (
<Ons.Page key={route.title}>
<Ons.Navigator
renderPage={this.renderPage}
initialRoute={{
title: 'First page',
hasBackButton: false
}}
/>
</Ons.Page>
);
}
};
SignUp.propTypes = {
'data-pageName': PropTypes.string.isRequired
};
export default SignUp;
Is this the right syntax in ES6? Have I missed something?

When using Ons.Navigator in react the two required properties are:
initialRoute - it should be an object.
renderPage - method which receives 2 arguments - route and navigator. The route should be an object similar to the initialRoute one. You provide that object when you are calling pushPage and similar methods.
It seems that you already know these 2, but there still 2 other things which you need to be careful about. They are not directly onsen related, but come up a lot when using react in general.
Whenever you have a list of dom elements (for example an array of Ons.Page tags) each of those should have a unique key property.
Whenever you use a method you need to make sure you are binding it if you need some extra arguments.
It seems you also know these two. So the only thing left is to make sure you follow them.
Your syntax is correct - the only thing missing is the route variable in SignUp.render. Maybe you originally copied the renderPage method and that is how you have a leftover Ons.Page.
If you're not putting the SignUp component inside some other navigator, tabbar or splitter then you don't actually need the Ons.Page in its render method. Those are the only cases when they are needed. If you it happens to have one of those components as a parent then you can just specify the key.
PS: I think there should be a React Component Inspector (something like this) which you can install - then I think you may be able to see the place where the error occurs. I think if you knew on which line the problem was you would have been able to solve it. :)

For me, with the object I was passing to initialRoute(), it needed a props property, which itself was an object with a key property. See the before and after below.
Before fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage}}
renderPage={this.renderPage}
/>
);
}
}
This was causing the following console warning:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Navigator`.
After fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage, props: {key: 'DataEntryPage'}}}
renderPage={this.renderPage}
/>
);
}
}
Notice that the difference I needed to make was the addition of , props: {key: 'DataEntryPage'}.
Feel free to check out this medium article for more information.

Related

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

Converting HTMLElement to a React Element, and keeping any event listeners

Rendering some JSON data into a set of collapsible HTML elements is EASY using a library like renderjson (npm package) for Vanilla JS
const data = { sample: [1, 2, 3, 4], data: { a: 1, b: 2, c: ["hello", null] } };
const rjson = renderjson(data);
document.getElementById('to-render').append(rjson);
In The react world
However, renderjson returns an HTMLElement, NOT A STRING. Please note that I'm not JUST trying to convert HTML strings into react HTML here. I'm trying to convert the HTMLElement into a real ReactElement.
So, I actually managed to do this, but since I'm parsing the HTMLElement into a string, all the onClick event listeners generated by renderjson on the + and - handles (to open or collapse the json) are LOST in the process....
Does anyone have any idea on how to convert a HTMLElement object into React Element, keeping all the original event handlers?
I guess, the real question here is: how can the renderjson package be "repackaged" to become React friendly?
Here is a codesandbox to make it easier to see what I'm talking about here.
Good question, I keep running into similar issues with non-React JS libraries that return DOM elements. These libraries should be ported to React, but if you don't want to do that, you can use findDomNode() to get a DOM (not React) node, and append your HTMLElement there. Please note that in the official docs, its use is discouraged because "it pierces the component abstraction". Anyways, in your example, you could add a wrapper class:
class JsonTreeWrapper extends Component {
componentDidMount() {
const renderedJson = renderjson(this.props.data);
const container = findDOMNode(this);
container.appendChild(renderedJson);
}
render() {
return <div/>;
}
}
what this does is render a div, and when the component mounts it finds the div and adds the HTMLElement generated by your library there. Simple.
In your App, you can use it like this:
class App extends Component {
render() {
return (
<div>
<h1> Inside the react world </h1>
<JsonTreeWrapper data={data}/>
</div>
);
}
}
This way your listeners will be working just fine.
Codesandbox didn't work for me, but you can check a demo here. I hope this helps!

React best practise / using parent component instance

let's imagine data that would look like that :
{
{
title: "great track",
tags: ["techno"]
},
{
title: "superb track",
tags: ["house", "90s"]
},
...
}
I render that in an html table, I have a component for the whole table, and a sub component for the tr (aka song title and tracks). so on each line I want to allow the users to be able to access a popup in which they can choose one or more tags for a song. I did it using reactstrap, it works ok.
I'm just a little disappointed by performance, it's quite ok, once it's built, but I saw how much longer it was to load when I added my modal on each line. So my first reflex, was to built only one modal in the parent component, and then use it from the sub component, and then I read articles on how, "one should not use the parent instance because it's bad"(tm).
I understand the point about dataflow, but in my example, having a modal waiting on each line while I'm sure I will never have two at the same time on screen feels like a waste of ressources.
Can anyone point me to an elegant way of building that kind of feature, in this particular context ?
Lifting state up to the parent component is a common practice in react, you can read articles in official documentation https://reactjs.org/docs/lifting-state-up.html
But there is one problem, when you use setState in your parent component, your songs table will render again and again, so you should care about it. One of the way is creating PureComponent for Songs table(if there is no changing in props, this component will not rerender)
I think, the code below is one of the way;
class Parent extends React.Component{
state={
tags: [],
songs: {
title: "great track",
tags: ["techno"]
},
{
title: "superb track",
tags: ["house", "90s"]
}
}
handlePopup(data){
this.setState({tags: data});
}
render(){
const {tags, songs} = this.state;
cons isShowModal = tags && tags.length
return (
<div>
{isShowModal && <Modal data={tags} />}
<SongsList data={songs} />
</div>
)
}
}
class Parent extends React.PureComponent{
render(){
const {data} = this.props;
return (
<table><tbody>...render songs data</tbody></table>
)
}
}
Of course using modal in child rows is a waste of resources. you need to add a modal to parent and use props to show/hide it. also, you should use the key prop for your list items.
By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there’s a difference.
but when children have keys, React uses the key to match children in the original tree with children in the subsequent tree.
it's good for performance.

In componentDidUpdate refs is undefined

I want to use Chart.js on my website. As you can see title, I'm using React.js. To use Chart.js, I need the canvas and context like this:
let context = document.getElementById('canvas').getContext('2d');
let chart = new Chart(context, ...);
so I design the component like this:
export function updateChart() {
let context = this.refs.chart.getContext('2d');
let chart = new Chart(context ,... );
...
}
export default class GraphChart extends React.Component {
constructor() {
super();
updateChart = updateChart.bind(this);
}
componentDidMount() {
updateChart();
}
render() {
return <canvas ref="chart" className="chart"></canvas>;
}
}
as you can see, I exported two things, update chart function and GraphChart class. Both will using in parent component like this:
import { updateChart } from './GraphChart';
import GraphChart from './GraphChart';
class Graph extends React.Component {
...
someKindOfAction() {
// update chart from here!
updateChart();
}
render() {
return (
<div>
<SomeOtherComponents />
<GraphChart />
</div>
);
}
}
then Parent class using exported updateChart function to update chart directly. It was working, but only first time. After unmount and mount the GraphChart component, it's refs are just empty.
Why refs is empty? And If I did wrong way, how can I get canvas context for initialize Chart.js?
Object refs is undefined, because this is not what you think it is. Try logging it.
The function you’re exporting is not bound to this of your component. Or perhaps it is, but to the last created instance of your component. You can never be sure that’s the mounted instance. And even if you are, you can not use multiple instances at the same time. So, I would dismiss this approach entirely.
Other than that, providing the function to alter some component’s state is exactly the opposite of what’s React is trying to accomplish. The very basic idea is that the component should know to render itself given some properties.
The problem you are trying to solve lies in the nature of Canvas API, which is procedural. Your goal is to bridge the gap between declarative (React) and procedural (Canvas) code.
There are some libraries which do exactly that. Have you tried react-chartjs? https://github.com/reactjs/react-chartjs
Anyways, if you’re wondering how the hell should you implement it the “React way”, the key is to declare properties your component handles (not necessarily, but preferably), and then to use component lifecycle methods (e.g. componentWillReceiveProps and others) to detect when properties change and act accordingly (perform changes to the canvas).
Hope this helps! Good luck!

Resources