Storing React Routes in Redux - javascript

I am using react-router v4 to generate dynamic routes which are loaded from an asynchronous API. The data retrieved from the API is passed through a function which will create the React routes, something similar to:
import Home from "containers/home"
import FeaturePage from "containers/featurePage"
response.map((item) => {
if (item.value.toLowerCase() === "home") {
return {
path: item.path,
component: Home,
exact: item.isExact
}
} else if (item.value.toLowerCase() === "featurePage") {
return {
path: item.path,
component: FeaturePage,
exact: item.isExact
}
} else {
// ...etc
}
});
Once the routes are loaded, they are added to the Redux store using an action-reducer as usual. This works well as the redux store supports functions.
However storing functions in the Redux store doesn't seem to be the best way forward. What would be a better implementation? I need to store the component which needs to be rendered, which is not serialisable.

Just store the component name as strings? (Not sure why you wanna add them to the store though)
You can probably use them later like this:
import Home from "containers/home"
import FeaturePage from "containers/featurePage"
// Assume the routes have been added to the redux store
const Routes = {
Home,
FeaturePage
};
response.map(({ component, path, exact }) => {
return (
<Route
key={ path }
exact={ exact }
path={ path }
component={ Routes[component] }
/>
);
});

A React component is made up of the state of the component (made up of state and props) and some behavior (like functions and html markup) which is modified based on the state. Once you have created the component, the behavior of the component will not change; only the state of the component will change.
I would think saving the state of the component would be enough to recreate the component at a later time and you would not need to save the component in Redux; instead save the state of the component in Redux.
Regarding saving your Routes in Redux, the same applies; you should save the state of the Route rather than the route itself in your Redux store. You can have a function which could take this state object (containing the path, isExact, component name etc.) and convert it to a real component for you.
Hope this helps!

Related

React - Wrapping all components in redux provider with store injection, Multi page app

I have created an hoc which looks like
const withStore = Component => {
const storeWrapper = props => (
<ReduxProvider store={store}>
<Component {...props } />
</ReduxProvider>
);
return storeWrapper;
};
Now all my separate components are wrapped by this(its multi page application) so that they can connect to same store, but that's not the case, one component is getting connected, changing with the incoming changes in props. But other components are totally disconnected not showing any change with data being changed in store.
Has anyone tried this approach earlier?
An example - if I have componentA and componentB, wrapped with this hoc.
Any store change from dispatch from componentA is getting reflected by componentA. Same for componentB. Main problem is when dispatch is from componentA and it is not getting read of componentB. vice-versa
As mentioned in #orel's comment, I resolved it by storing store in global variable.
In store.js file, to keep single instance everywhere, I added a condition
if (!window.store) {
window.store = store;
}
It works like charm now!!

Do I need to use Redux or Context API

I have an application where users log in first as usual. My app has several screens which are navigated by react-native-navigation.
On every screen other than login, I need to know which user is using my app since the content is specialized by his/her uniqueID. I get that uniqueID when the user successfully login but I don't know how to pass this uniqueID to other screens.
Do I need to use Redux or context API in order to handle this problem or is there another way to pass this data between screens back and forth without changing the project?
Here is my App.js:
import React, { Component, PropTypes } from 'react';
import { AppNavigator } from './components/Navigator';
class App extends React.Component {
render() {
return (
<AppNavigator />
);
}
}
export default App;
Here is my Navigator component:
const Stack = createStackNavigator({
Main: { screen: MainScreen },
Login: {screen: LoginScreen},
Profile: {screen: ProfileScreen},
NewSurvey: {screen: NewSurveyScreen},
},
{
initialRouteName: 'Login',
headerMode: 'none',
navigationOptions: {
headerVisible: false,
gesturesEnabled: false,
}
})
export const AppNavigator = createAppContainer(Stack);
To pass data to other screens you can use React Navigation navigate method and passing some data inside of it.
i don't know how you stored you Data whether using a database like Realm or SQLite etc... but whenever you fetch data from there and you want to pass it to other screens do this like below:
this.props.navigation.navigate('SecondPage', {
//your data goes here
});
for example :
this.props.navigation.navigate('Homescreen', {
username: this.state.username,
});
and then you can get that data like below:
const username = navigation.getParam('username', 'Default'); //default is a value that will be used if there was no username passed
I think you are using React Navigation as per the code shared. I would suggest you implement auth flow as suggested here and use Redux to set/access the uniqueID. Redux serves a lot more stuff and is used widely for state management.
You can set uniqueID in _signInAsync() and then access it through. LocalStorage can be an option but not a feasible solution as accessing values from LocalStorage may affect app performance.
redux or react-context
“Redux is a predictable state container for JavaScript apps.”
“Context provides a way to pass data through the component tree
without having to pass props down manually at every level.”
For low-frequency updates like locale, theme changes, user authentication, etc. the React Context is perfectly fine. But with a more complex state which has high-frequency updates, the React Context won't be a good solution. Because, the React Context will trigger a re-render on each update, and optimizing it manually can be really tough. And there, a solution like Redux is much easier to implement.
When to use Context API
If you are using Redux only to avoid passing props down to deeply nested components, then you could replace Redux with the Context API
When to use Redux
Redux is a predictable state container, handling your application's logic outside of your components, centralizing your application's state, using Redux DevTools to track when, where, why, and how your application's state changed, or using plugins such as Redux Form, Redux Saga, Redux Undo, Redux Persist, Redux Logger, etc.
In This case we can use Redux.

React Reuse Fetch JSON data throughout components

Hi there I am newbie to React and learning about reusable functions throughout my project.
I would like to fetch my JSON data but not have to call it everytime in my component.
App.js
import React, { Component } from 'react';
import { Route, NavLink, HashRouter } from "react-router-dom";
import logo from '../assets/logo.png';
import './app.css';
import About from "../about/about";
import Services from "../services/services";
import Testimonials from "../testimonials/testimonials";
import Contact from "../contact/contact";
class App extends Component {
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
render() {
return (
<HashRouter>
<div className="container">
<div className="header">
<div className="App-logo"><NavLink exact to="/"><img src={logo} alt="logo" /></NavLink></div>
<nav className="Nav-Desktop">
{this.state.items.map((item, index) => (
<div key={index}>
{
item.links.map((link, i) => (
<NavLink key={i} exact to={link.url}>{link.text}</NavLink>
))}
</div>
))}
{
this.state.items.map((item, index) => {
return <div key={index}></i><strong> {item.mainContact.phone}</strong></div>
})
}
</nav>
</div>
<main className="content">
<Route exact path="/" component={About}/>
<Route path="/services" component={Services}/>
<Route path="/testimonials" component={Testimonials}/>
<Route path="/contact" component={Contact}/>
</main>
{this.state.items.map((item, index) => {
return <footer key={index}>© Copyright {item.title} {(new Date().getFullYear())}</footer>
})
}
</div>
</HashRouter>
);
}
}
export default App;
I am successfully mapping my data and displaying it, but I have other files that include this snippet
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
I have tried exporting the getItems() like so in a helper.js file and importing the file import { getItems } from '../helpers/helpers'; however the code did not work properly and got stuck at Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
export function getItems() {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
If anyone can give me pointers as to the error / right way to go about this that would be helpful. Cheers
Two things you need to know when you want to reuse the data instead of calling fetch again and again
Do fetch call in top most component i.e., parent component and pass down data to all the children, children to children components but, do remember this will be hectic for you. This approach is good when you are building small application which will be like max to max 50 components. But when your application grows big this is not a recommended way of reusing the data across components.
Use Redux state management library for data reusability across components. This acts like a centralised store for your application. This is used mostly in every React app these days. With Redux you can make an action call in parent component and that action will actually fetch the data and pass it to the reducer. So now reducer will set the data in Redux store. Now the data is accessible in every component by getting the data from Redux store using state.get. So you can call redux action like this.props.getItems(); wherever you need the data in the component and the component mapStateToProps will make that data available to your component as props
How to get the data from Redux store?
Define a function mapStateToProps in component and get the data from Redux store using state.get and return it in the function. Pass the mapStateToProps to connect method. You will be connecting your component to Redux HOC component called connect. This connect method accepts actions and Redux state and make them available to the component as props.
Regarding your issue
Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
The reason you get this issue because this isn’t available inside the exported getItems function.
What you need to do to fix the issue is pass this to getItems function as a parameter
Note: Here this is a current context
import { getItems } from '../helpers/helpers';
componentDidMount(){
getItems(this);
}
helpers.js:
the below function is treated as a normal JavaScript function. This function no wr belongs the component to bind it in component constructor. But in order to play with the state of the component you need to pass the component context this. But this is old way, now a days we all use Redux to avoid these old concepts
export function getItems(this) {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
This way you can reuse this function and do the setState.
But do remember these solutions will make complex when your application grows big in future. So I would argue you to go with Redux state management library from now to avoid hectic migration in future :)
Excuse me if there are any typo errors because I am answering from my mobile.
You need to pass the context to your helper function. Considering you're still importing the function from your helper, on your componentDidMount, it can be like:
componentDidMount(){
getItems.call(this);
}
So good job with the fetching, there are a lot of paths you can take with sharing data across components, the two main ones you can look into are:
Parent state holds the data, passes it through props
A Store (Mobx, Redux) holds the data, and you inject it into your components as needed
I'm not going to give you the tutorial on how to do them all but I'm assuming the first option will be best for you right now.
If your components are using the same data, they should theoretically have the same parent component in a structure similar to this:
Page Component (Overarching component, lays out the page structure)
Header
Component 1
Component 2
Footer
So you'll do this call on the mount of Page component and pass this.state.items as a prop to the other components that need it... Stores have a similar process they just save the double handling of props :)
Since you're accessing/modifying state in your getItems() method, you should bind it in your constructor like this:
constructor() {
super(props);
this.state = {
items : []
};
this.getItems = this.getItems.bind(this);
}
or you can simply declare it with arrow function so you don't have to bind it:
getItems = () => {
// some codes here
}
Another thing, the only ones who can access/modify the state of a compoenent is its own methods. If you want to use it outside of itself, import those components inside your component as children then pass your getItems() method as prop.
render() {
return (
<HashRouter>
<div className="container">
// some other codes
<ChildComponent getItems={this.getItems} />
</div>
</HashRouter>
);
}

How to dispatch redux actions using react-router v4?

I'm trying to combine react-router v4, react, and redux. Because react-router tracks the URL, I've opted to keep that piece of state out of the redux-model.
But i still need a way to to dispatch a redux action when a route change happens by react-router. Where is the best place to do that?
My first attempt was to put it in the onClick attribute of react-router's Link:
render() {
// link config
const links = this.props.photo.album( album => {
<Link key={album.name}
to=`/album/${album.name}`
onClick={() => this.props.dispatchAction(album.name)} />
})
// route config
return (
<div>
{links}
<Route path={`/album/:albumName`} component={Album}/>
</div>
)
}
The idea is that, when a user clicks a link, the dispatchAction() will update the redux state and then the Album component gets loaded.
The problem is that if a user navigates to the URL directly (eg /album/a1), the action is never dispatched, since the link is technically never clicked.
Because of this I removed the onClick portion of the Link, and moved the dispatchAction to the lifecycle methods of the Album component:
class Album extends Component {
// invoked when user navigates to /album/:albumName directly
componentDidMount() {
this.props.dispatchAction(this.props.match.params.albumName)
}
// invoked whenever the route changes after component mounted
componentWillReceiveProps(nextProps) {
if (this.props.match.params.albumName != nextProps.match.params.albumName) {
this.props.dispatchAction(nextProps.match.params.albumName)
}
....
}
Now whenever the Album component is mounted or its properties changed, it will dispatch the redux-action. Is this the correct approach for combining these libraries?
react-router-redux does this for you by applying a middleware on your store that dispatches actions on route changes, also on the initial route change. It's definitely the cleanest approach.
The only downside is it's still alpha but we have been using it without any issues for a while. It is also part of the react-router additional packages repo for a while.
You could create a custom Route component that dispatches your action in componentDidMount:
class ActionRoute extends React.Component {
componentDidMount() {
const { pathname } = new URL(window.location.href);
const [albumName] = pathname.split('/').slice(-1);
this.props.dispatchAction(albumName);
}
render() {
return <Route {...this.props} />
}
}
This will now dispatch your action whenever the route is loaded. Composability FTW!.
Side note: if you use ActionRoute for parameterized routes, (eg /album/1 and /album/2), you'll need to also dispatch the action in componentDidUpdate as the component isn't unmounted / remounted if you navigate from /album/1 to /album/2.

Redux and React - Keeping component props in sync with state via #connect

I have an application set up using React and Redux such that I have a root application component wrapped around a provider (which is passing the store down so that my child components can access state and dispatch) like so:
<Provider store={Store}>
<RootComponent />
</Provider>
My RootComponent essentially uses a JSON file to render Template components that get passed a key and title (
<Application children={children} />
Within one of the children, I've hooked up the state of the application to the Component using the #connect decorator and I'm firing off a dispatcher upon construction to update my 'content' prop like so:
#connect(state => ({content: state.content}))
export default class DynamicText extends Component {
constructor(props) {
super(props);
var Content = require(`./jsonContent`);
props.dispatch(loadContent(Content[props.title].elements));
}
**actions.js**
export function loadContent(content) {
return {
type: 'LOAD_CONTENT',
content
};
}
**reducers.js**
const initialState = {
route: null
};
export default function routeReducer(state = initialState, action) {
switch (action.type) {
case LOAD_CONTENT:
return Object.assign({}, state, {
content: action.content
});
default:
return state;
}
The issue I'm having is that when booting up my application, the 'content' prop is undefined - upon page refresh however, it populates with the updated 'content' data from the JSON file. I think the issue may be occurring because initially the state.content referenced in my #connect decorator is undefined, however I was hoping that the immediate dispatch on the constructor would solve this issue.
Does anyone have experience or recommendations on how I can get this working without a refresh? Is #connect really the best option here or are there alternatives?
You need to hydrate your store before passing it down to Provider providing an initial state to it so that when your app "boots" your store already has the data available.
Using connect is the right way to go after that.
If you're running a server side rendered application you can put your json contents in a property attached to window on first request then empty it after store has been hydrated.
Something along the lines of window.__BOOTSTRAP__.
store = createStore(appReducers, { content: yourJsonFileContents });
<Provider store={store}>
<RootComponent />
</Provider>

Categories

Resources