My React navigation is dynamic, warning : You should only render one navigator explicitly in your app - json

I'm struggling to make my dynamic navigation working in my react-native app.
Here is what I have on my AppNavigation.js :
import {
createDrawerNavigator,
createStackNavigator,
createSwitchNavigator, DrawerItems, SafeAreaView
} from 'react-navigation'
import LoginScreen from '../screens/LoginScreen'
import ProfileScreen from "../screens/ProfileScreen";
import TemplatesScreen from "../screens/TemplatesScreen";
import AuthLoadingScreen from "../screens/AuthLoadingScreen";
import React from "react";
import {Button, Icon} from "native-base";
import {ScrollView} from "react-native";
import NewFilmScreen from "../screens/NewFilmScreen";
import SettingsScreen from "../screens/SettingsScreen";
import LogoutScreen from "../screens/LogoutScreen";
import TemplateWorkflowContainer from "./TemplateWorkflowContainer";
const WorkflowContainer = createStackNavigator(
{
TemplateContainer: {
screen: TemplateWorkflowContainer
}
},
{
headerMode: 'none',
}
);
// drawer stack
const AppNavigation = createDrawerNavigator({
TemplatesScreen: {screen: TemplatesScreen},
NewFilm: {screen: NewFilmScreen},
ProfileScreen: {screen: ProfileScreen},
SettingsScreen: {screen: SettingsScreen},
LogoutScreen: {screen: LogoutScreen}
},
{
drawerBackgroundColor: '#ff4559',
// Default config for all screens
headerMode: 'none',
initialRouteName: 'TemplatesScreen',
contentOptions: {
activeTintColor: '#fff',
inactiveTintColor: '#fff',
itemsContainerStyle: {
marginVertical: 0,
},
itemStyle: {
flexDirection: 'row-reverse',
},
iconContainerStyle: {
opacity: 0.8,
}
},
contentComponent: props =>
<ScrollView>
<SafeAreaView forceInset={{top: 'always', horizontal: 'never'}}>
<Button transparent>
<Icon name='close' style={{fontSize: 40, color: 'white'}} onPress={() => {
props.navigation.closeDrawer()
}}/>
</Button>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
});
const WrapperStack = createStackNavigator({
AppDrawer: AppNavigation,
WorkflowContainer: WorkflowContainer
},
{
headerMode: 'none'
}
);
// Manifest of possible screens, when the user sign in the loginStack will be unmount to never logged out the user with
// the back button
const PrimaryNav = createSwitchNavigator({
AuthLoading: {screen: AuthLoadingScreen},
Auth: {screen: LoginScreen},
App: {screen: WrapperStack}
}, {
initialRouteName: 'AuthLoading'
});
export default PrimaryNav;
My drawer is fine. The problem is on the WorkflowContainer. This is a navigation like this :
import React, { Component } from "react";
import { createStackNavigator } from "react-navigation";
import TemplateWorkflowNavigator from "./TemplateWorkflowNavigator";
export default class TemplateWorkflowContainer extends Component {
constructor(props) {
super(props);
this.state = {
content: null
};
}
generateScreens = data => {
const stack = {};
stack["0"] = {
screen: TemplateWorkflowNavigator,
navigationOptions: () => ({
title: data.title,
gesturesEnabled: true
})
};
for (let i = 0; i < data.scenes.length; i++) {
let screenNumber = data.scenes[i].priority + 1;
stack[screenNumber] = {
screen: TemplateWorkflowNavigator,
navigationOptions: () => ({
title: data.scenes[i].name,
gesturesEnabled: true
})
};
}
return stack;
};
renderStackNavigo = aTemplate => {
const TemplateStackNavigor = createStackNavigator(
this.generateScreens(aTemplate), {headerMode: 'none'}
);
return <TemplateStackNavigor screenProps={aTemplate}/>;
};
render() {
return this.props.navigation.state.params.json && this.renderStackNavigo(this.props.navigation.state.params.json);
}
}
It's dynamic, throught the this.props.navigation.state.params.jsoni got back a JSON like this :
{
"id": 5,
"title": "toto",
"dolly": 74,
"name": "toto",
"conditions": [
{
"name": "Calm",
"desc": "test",
"priority": 0
}
],
"medias": [
{
"path": "a_path_here",
"mobile_path": "a_path_here",
"size": 80851,
"type": "preview"
}
],
"scenes": [
{
"name": "Intro",
"priority": 0,
"conditions": [
{
"name": "smile",
"desc": "test",
"priority": 0
}
],
"medias": [
{
"path": "a_path_here",
"mobile_path": "a_path_here",
"size": 80851,
"type": "preview"
}
],
"elements": [
{
"name": "Name",
"priority": 0,
"type": "text",
}
]
}
]
}
It's working when I call this
this.props.navigation.navigate("TemplateContainer", { json: path });
But I have this warning :
You should only render one navigator explicitly in your app, and other
navigators should by rendered by including them in that navigator.
I tried a lot of things, but I'm so new on react native, nothing worked.
How can I make this navigation works with no warning ? What changes do I have to apply ?

As my assumption, your TemplateWorkflowContainer will look like this
export default class TemplateWorkflowContainer extends Component {
static router = null;
...
renderStackNavigo = aTemplate => {
const TemplateStackNavigor = createStackNavigator(
this.generateScreens(aTemplate), {headerMode: 'none'}
);
TemplateWorkflowContainer.router = TemplateStackNavigor.router;
return <TemplateStackNavigor screenProps={aTemplate}/>;
};
...
}

Related

Why does react refuse to convert jsx to HTML component and just returns [object Object]?

Please find // the console log I am talking about in the below code.
This console log returns string So its not that I am passing object here. but even then react doesn't recognise the jsx and adds object Object in HTML.
Output I am getting is:
import React, { useEffect, useState } from "react";
function App() {
const [file, setFile] = useState();
useEffect(() => {
setFile(
JSON.parse(`{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}`)
);
}, []);
return (
<>
{/* <import file component> */}
<JsonViewer file={file} />
</>
);
}
function JsonViewer({ file }: any) {
const [fileContent, setFileContent] = useState<any>();
useEffect(() => {
if (file) {
setFileContent(getJsonData(file, ""));
}
}, [file]);
function getJsonData(data: any, idToAppend: string) {
let t: any = [];
if (data.length) {
console.log(data.length);
for (let i in data) {
let idToA = `${idToAppend}${i.toString()}`;
t.push(getJsonData(data[i], idToA))
}
}
else {
Object.entries(data).forEach(([key, value], i) => {
const idToA = `${idToAppend}${i.toString()}`;
if (typeof value === "object") {
let a: any = value
t.push(
<div key={i} id={idToA}>
<button
onClick={(e) => {
let ele = document.getElementById(idToA);
if (ele) {
ele.innerHTML += getJsonData(value, idToA);
}
}}
>
{key}
</button>
</div>
);
}
else {
// the console log I am talking about
console.log(typeof value);
t.push(
<div key={i}>
<div
id={idToA}
>
{key}:{value}
</div>
</div>
);
}
}
);
}
return t;
}
return (
<div>
{fileContent}
</div>
);
}
export default App;
The reason why you are getting objects is because the current procedure is creating nested t lists. You can see the output here: https://codesandbox.io/s/youthful-sunset-ru8ic?file=/src/Old.tsx
An alternative, working approach:
import React from "react";
const DATA = {
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
};
interface theDat {
data: {
short_name: string;
name: string;
icons: Icons[];
start_url: string;
display: string;
theme_color: string;
background_color: string;
};
};
type Icons = {
src: string;
sizes: string;
type: string;
};
const App = () => {
const JsonInterpreter: Function = ({data}: theDat) => {
let elements = [];
Object.keys(data).forEach((dat, i) => {
let value = data[dat];
console.log(`this: ${dat} ${typeof value}`)
if (typeof value === 'string') {
elements.push(
<p key={i}>{dat} {value}</p>
)
} else if (typeof value === 'object') {
elements.push(
<p key={i}>{dat}</p>
);
console.log(value);
let nested = [];
(value as []).forEach((d, j) => {
let keys = Object.keys(d);
if (['src', 'type', 'sizes'].every(e => keys.includes(e))) { /* typeguard for icons */
nested.push(
<p key={`n-${j}`}>{JSON.stringify(d)}</p>
);
};
})
elements.push(...nested);
};
});
return elements;
};
return (
<>
<JsonInterpreter data={DATA} />
</>
);
};
export default App;
Working CodeSandbox: https://codesandbox.io/s/youthful-sunset-ru8ic?file=/src/App.tsx:0-1789

How to use map on json response returned by a REST API with ReactJs

I've a json. The only thing I want is title from the json.
{
"projects": [
{
"id": 1,
"title": "Bike Servicing System",
"language": "JavaFX",
"requires": "JDK 8"
},
{
"id": 2,
"title": "Air Traffic Controller",
"language": "JavaFX",
"requires": "JDK 8"
},
{
"id": 3,
"title": "Program Counter",
"language": "JavaFX",
"requires": "JDK 8"
}
],
"profile": {
"name": "typicode"
}
}
I am using fetch and componentDidMount. I want to do it musing map method to iterate through. Though I don't need <ul> and <li> tags really. I will remove them later. My React code is
import React, { Component } from "react";
class ProjectStack extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
};
}
componentDidMount() {
fetch("http://my-json-server.typicode.com/tmtanzeel/json-server/projects/")
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json.projects
});
});
}
render() {
var { isLoaded, items } = this.state;
var i=0;
if (!isLoaded) return <div>Loading...</div>;
return (
<div>
<ul>
{
items.map(item => (
<li key={item[i++].id}>
Projects: {item[i++].title}
</li>
))
}
</ul>
</div>
);
}
}
export default ProjectStack;
Apparently, there is something that I don't know because I am getting this error.
PS: This question is different from mine
The JSON of the URL you are fetching is this:
[
{
"id": 1,
"title": "Bike Servicing System",
"language": "JavaFX",
"requires": "JDK 8"
},
{
"id": 2,
"title": "Air Traffic Controller",
"language": "JavaFX",
"requires": "JDK 8"
},
{
"id": 3,
"title": "Program Counter",
"language": "JavaFX",
"requires": "JDK 8"
},
{
"id": 4,
"title": "Dove Tail",
"language": "JavaFX",
"requires": "JDK 8"
}
]
So for correctly set the data in the state you need to:
componentDidMount() {
fetch("http://my-json-server.typicode.com/tmtanzeel/json-server/projects/")
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
});
});
}
Besides correcting the error they have already told you related the map loop.
I found 2 mistakes from your code.
The first one is you didn't check the response data.
fetch("http://my-json-server.typicode.com/tmtanzeel/json-server/projects/")
.then(res => res.json())
.then(json => {
console.log(json); // it is an Array not object.
this.setState({
isLoaded: true,
items: json
});
});
And the second is you didn't use the map() properly.
/*{
items.map(item => (
<li key={item[i++].id}>
Projects: {item[i++].title}
</li>
))
}*/
// items has objects, so you should use map() like this.
{
items.map(item => (
<li key={item.id}>
Projects: {item.title}
</li>
))
}
The below code is one way to achieve what you want.
class ProjectStack extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false
};
}
componentDidMount() {
fetch("http://my-json-server.typicode.com/tmtanzeel/json-server/projects/")
.then(res => res.json())
.then(json => {
console.log(json);
this.setState({
isLoaded: true,
items: json
});
});
}
render() {
const {
isLoaded,
items
} = this.state;
console.log(items);
if (!isLoaded) return ( < div > Loading... < /div>);
return ( <
div >
<
ul > {
items.map(item => ( <
li key = {
item.id
} > Projects: {
item.title
} < /li>
))
} <
/ul> <
/div>
);
}
}
ReactDOM.render( <
ProjectStack / > , document.getElementById('root')
)
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

React Native render nested Json

After loading a JSON object and adding it to this.state, I am not able to access levels below the first level. Given this JSON file:
{
"ts": 1530703572,
"trend": {
"val": 0,
"text": "gleichbleibend"
},
"baro": 1011.3453734999999,
"temp": {
"out": {
"f": 85.9,
"c": 29.9
}
},
"hum": {
"out": 28
},
"wind": {
"speed": {
"mph": 2,
"kmh": 3.2
},
"avg": {
"mph": 3,
"kmh": 4.8
},
"dir": {
"deg": 192,
"text": "SSW"
}
},
"rain": {
"rate": 0,
"storm": 0,
"day": 0,
"month": 0,
"year": 451.358
},
"et": {
"day": 88,
"month": 81,
"year": 1802
},
"forecast": {
"val": 6,
"rule": 45,
"text": "Zunehmend wolkig bei gleichbleibender Temperatur."
},
"sun": {
"uv": 6.2,
"rad": 779,
"rise": "4:27",
"set": "20:35"
}
}
the following results:
{this.state.weatherList.ts} works: 1530703572
{this.state.weatherList.temp.out.c} "TypeError: Undefined is not an object (Evaluating: 'this.state.weatherList.temp.out')
The Code:
export default class Weather extends React.Component {
constructor(props) {
super(props)
this.state = {
weatherList: []
}
getPlannerData().then(data => {
this.setState({
weatherList: data
})
})
}
return (
<ScrollView>
<View>
<View>
<Text>{this.state.weatherList.temp.out.c}</Text>
</View>
</View>
</ScrollView>
)
}
}
async function getPlannerData() {
let data = await fetchApi('url')
return data
}
async function fetchApi(url) {
try {
let response = await fetch(url)
let responseJson = await response.json()
console.log(responseJson)
return responseJson
} catch (error) {
console.error(error)
return false
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22
},
sectionHeader: {
paddingTop: 2,
paddingLeft: 10,
paddingRight: 10,
paddingBottom: 2,
fontSize: 14,
fontWeight: 'bold',
backgroundColor: 'rgba(247,247,247,1.0)'
},
item: {
padding: 10,
fontSize: 18,
height: 44
}
})
The question is how I can alter the code, so I can access the nested elements like "temp".
I tried it with renderItem and {this.state.weaetherList.map((item, i) => without success.
Thanks in advance!
Before your weatherList object is set, anything further than one level down your object will result in an error. this.state.weatherList.ts will not give an error, since it will just be undefined before your request is finished.
You could e.g. keep a state variable loading and only render when you request has finished to get around this.
Example
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
weatherList: {}
};
getPlannerData().then(data => {
this.setState({
loading: false,
weatherList: data
});
});
}
render() {
const { loading, weatherList } = this.state;
if (loading) {
return null;
}
return (
<ScrollView>
<View>
<View>
<Text>{weatherList.temp.out.c}</Text>
</View>
</View>
</ScrollView>
);
}
}

Iterate through JSON Object to get all objects with property

I am trying to iterate through the following JSON document in order to get the names of skating rinks:
I can get one name; however, what I am trying to do is loop through all of the entries (there are 253) and return a list of all the names.
Here is my React component:
class Patinoire extends Component {
constructor(props) {
super(props);
this.state = { patinoires: [] };
}
componentDidMount() {
var url = 'http://localhost:3000/patinoires'
fetch(url).then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
return response.json();
})
.then(data => this.setState ({ patinoires: data.patinoires }));
}
render() {
var patinoires = this.state.patinoires;
var pjs2 = Object.values(patinoires);
var pjs3 = pjs2.map(x => x["2"].nom);
return <div>{pjs3}</div>
}
}
Right now, when using {pjs3}, I get the name of 3rd skating rink of the JSON document. How can I loop through all the entries and return the name property of all the entries?
EDIT: here is a sample of the data
{
"patinoires": {
"patinoire": [
{
"nom": [
"Aire de patinage libre, De la Savane (PPL)"
],
"arrondissement": [
{
"nom_arr": [
"Côte-des-Neiges - Notre-Dame-de-Grâce"
],
"cle": [
"cdn"
],
"date_maj": [
"2018-01-12 09:08:25"
]
}
],
"ouvert": [
""
],
"deblaye": [
""
],
"arrose": [
""
],
"resurface": [
""
],
"condition": [
"Mauvaise"
]
},
{
"nom": [
"Aire de patinage libre, Georges-Saint-Pierre (PPL)"
],
"arrondissement": [
{
"nom_arr": [
"Côte-des-Neiges - Notre-Dame-de-Grâce"
],
"cle": [
"cdn"
],
"date_maj": [
"2018-01-12 09:08:25"
]
}
],
"ouvert": [
""
],
"deblaye": [
""
],
"arrose": [
""
],
"resurface": [
""
],
"condition": [
"Mauvaise"
]
}
]
}
}
You can use Array.prototype.reduce() to flatten the result data with combination of Array.prototype.map() or Array.prototype.forEach().
Here is a running example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
patinoires: {
patinoire: [
{
nom: ["Aire de patinage libre, De la Savane (PPL)"],
arrondissement: [
{
nom_arr: ["Côte-des-Neiges - Notre-Dame-de-Grâce"],
cle: ["cdn"],
date_maj: ["2018-01-12 09:08:25"]
}
],
ouvert: [""],
deblaye: [""],
arrose: [""],
resurface: [""],
condition: ["Mauvaise"]
},
{
nom: ["Aire de patinage libre, Georges-Saint-Pierre (PPL)"],
arrondissement: [
{
nom_arr: ["Côte-des-Neiges - Notre-Dame-de-Grâce"],
cle: ["cdn"],
date_maj: ["2018-01-12 09:08:25"]
}
],
ouvert: [""],
deblaye: [""],
arrose: [""],
resurface: [""],
condition: ["Mauvaise"]
}
]
}
};
}
renderData = () => {
const { patinoires } = this.state;
const markup = patinoires.patinoire.reduce((result, current) => {
current.nom.map(n => {
return result.push(<div>{n}</div>);
});
return result;
}, []);
return markup;
}
render() {
return <div>{this.renderData()}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Try this:
var pjs2 = Object.values(patinoires);
var names = pjs2.reduce((accumulator, elem) => {
if (elem.patinoire) {
elem.patinoire.forEach(part => {
if(part.nom) {
part.nom.forEach((name) => accumulator.push(name));
}
});
}
return accumulator;
}, []);

Import JSON data in to array in REACTJS

So ive been looking at this for a couple hours now trying to get my head around it but I just cant figure it out.
I have a json file located at '/src/data/topbar.json' which i want to include in my topbar-container component which will be used to generate the top menu.
What am I doing wrong here?
topbar.json:
{
"topbarLinks": [
{
"id": 1,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-home",
"text": "home",
"link": "/"
},
{
"id": 2,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-euro",
"text": "Pricing",
"link": "/pricing"
},
{
"id": 3,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-exclamation-sign",
"text": "Help",
"link": "/help"
},
{
"id": 4,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-question-sign",
"text": "FAQ",
"link": "/faq"
},
{
"id": 5,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-edit",
"text": "Register",
"link": "/register"
},
{
"id": 6,
"icon": "header__topbar__list__link__icon glyphicon glyphicon-share",
"text": "Login",
"link": "/login"
}
]
}
topbar-container.js
import React, { Component } from 'react';
import './topbar-container.scss';
import Link from '../topbar-link/topbar-link';
require ('../../data/topbar.json');
class TopbarContainer extends Component {
constructor() {
super();
this.State = {
topbarLinks: []
}
}
componentDidMount() {
fetch('../../data/topbar.json')
.then(results => {
return results.json();
}).then(data => {
let topbarLinks = data.results.map((topbarLinks, key) => {
return (
<Link
key={topbarLinks.id}
text={topbarLinks.text}
icon={topbarLinks.icon}
link={topbarLinks.link}
/>
)
})
})
}
render() {
return (
<div className="container-fluid header__topbar">
<div className="row">
<div className="container">
<ul className="header__topbar__list">
{this.state.topbarLinks}
</ul>
</div>
</div>
</div>
);
}
}
export default TopbarContainer;
You can't fetch a local JSON file, you either have to import it, or setup a webserver that will serve that JSON file
import myJson from '../../data/topbar.json';
Then just map over it and don't forget to setState
componentDidMount() {
let topbarLinks = myJson.topbarLinks.map((topbarLinks, key) => {
return (
<Link
key={topbarLinks.id}
text={topbarLinks.text}
icon={topbarLinks.icon}
link={topbarLinks.link}
/>
)
})
this.setState({topbarLinks: topbarLinks}); // <--
//or just this.setState({ topbarLinks });
}
and as somebody else noted this.state has to be lowercase
topbar.json:
export default {
"topbarLinks": []
}
then you can simply import it without fetch
import data from '../../data/topbar.json'
let topbarLinks = data.results.map((topbarLinks, key) => {
return (
;
I don't think that State should be capitalized in this.State in your constructor.
Change map function to data.topbarLinks.map to access json data.
let topbarLinks = data.topbarLinks.map((topbarLinks, key) => {
return (
<Link
key={topbarLinks.id}
text={topbarLinks.text}
icon={topbarLinks.icon}
link={topbarLinks.link}
/>
)
})
And then set state
this.setState({topbarLinks: topbarLinks});
Change your initial state as this.State to this.state in constructor.