Pass object through Link in react router - reactjs

I found this question, which describes exactly what I was looking for...
Pass object through Link in react router
Is it possible to pass an object via Link component in react-router?
Something like:
<Link to='home' params={{myObj: obj}}> Click </Link>
In the same way as I would pass props from the Parent to Child component.
If it's not possible what is the best way of achieving this:
I have a React + Flux app, and I render the table with some data. What I am trying to do is when I click on one of the rows it would take me to some details component for this row. The row has all of the data I need so I thought it would be great if I could just pass it through Link.
The other option would be to pass the id of the row in the url, read it in the details component and request the data from the store for by ID.
Not sure what is the best way of achieving the above...
I agree with the author's conclusion, meaning: instead of passing an object, we should pass an id. I am however struggling with where on the next component I should perform that lookup, possibly in an onload method where I define mapDispatchToProps.
However, I don't know how to access the state from there to see if the object is in the state so I can retrieve it from an api call if it isn't in the state. Does that belong here or in the action? If it is in the action, how do I get it there. This seems like it would be a very basic pattern and I am missing something.

You use it with redux-thunk and you can make action for the router.
I mean something like this
export const boundAllStreams = (nextState, replaceState) => reqStreams(nextState.params.game);
So you can see I use the params game and change the state with
export const reqStreams = game => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${game}&client_id=${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.streams);
}
});
});
return {
type: types.RECEIVE_STREAMS,
payload: promise
};
};
Here this is my reducer where I got my params from the router action.
After you need to do something like this, you bind your action and make it a object.
const boundRouteActions = bindActionCreators(routeActions, store.dispatch);
And finally in the router you can dispatch the action with the onEnter api from react-router
<Route path=":game">
<IndexRoute
component={StreamsApp}
onEnter={boundRouteActions.boundAllStreams} />
</Route>
Hope that can help you ;). I know I just show you code but I'm sure that can help yo figured out how to implement this ;)

Related

nextjs pass current route to the higher order cmponent as parametre

i am having a hoc withAuth.js
in which i am passing a component and second parameter is current route which would render if the condtion fails
const Detail = (props) => {
return(
<>
<div> this is the my account inside ..... </div>
</>
)
};
export async function getServerSideProps({req ,res}) {
// Call an external API endpoint to get posts.
// You can use any data fetching library
// console.log('request object ', req.headers);
// retun the props.api bcoz client side has no access to the baseurl from the server
return {
props:{}
}
}
export default withAuth(Detail, loginRedirectPath);
my question is that how to pass the current route the hoc
edit
hi i have solve this problem by managing route history
I don't believe you need to actually pass this as a parameter, but you can if you so wish. As per the docs, you can use the useRouter() hook (or another method such as importing the Router object) to get the current pathname. I believe this will work on either the component or HOC when using the hook, although I may be wrong on this. Regardless, using next/router to get the pathname is the approach here!

How to make react app listen to redux store conditionally?

I have a React app for University Management.
Below is my router:
<Route path="selecting/" component={SelectUniversity}>
<Route path="myUniversity" component={MyUniversity} />
<Route path="studentdetails" component={AddStudentDetails} />
<Route path="payment" component={Payment} />
</Route>
the flow is==>MyUniversity==>AddStudentDetails==>Payment
As per this, everything is working as expected
All the three components MyUniversity, AddStudentDetails, Payment are extensivly using redux store
MyUniversity's mapStateToProps is as follows
function mapStateToProps(state) {
const { results } = state.results
const studentData = state.results.studentData
return {
results,
studentData,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ ...Actions, ...studentActions }, dispatch)
}
there are many store variables involved, this is for sample purpose
Similarly, separate mapstaetoprops and mapdispatchtoprops for other two component.
Now the requirement is (for some unavoidable reasons):-
if a user directly lands on myuniversity page with a id like this below:-
http://mywebsite.com/myuniversity/9999,
I need to get the data associated with 9999 (which am already getting) and execute the same flow.
my updated router
<Route path="selecting/" component={SelectUniversity}>
<Route path="myUniversity" component={MyUniversity} />
<Route path="myUniversity/:unidetails" component={MyUniversity} />
<Route path="studentdetails" component={AddStudentDetails} />
<Route path="payment" component={Payment} />
</Route>
Once I get the data how can I update the redux store so that the existing flow will work as expected.
I know I can dispatch as many actions as i want once we get the data from ajax call but like I said there are 15-20 different state variables are involved for each of the three component. So, it does not seem like a scalable approach to fire so many dispatchers on each component load.
Approach 2
So I came up with another approach:-
1. Define a new reducer.
2. Upon getting data store the entire ajax result in your desired format in the state.
3. Now go to the mapstatetoprops of each of the three components and add conditions on every every prop level whether get data from pevious reducer or current reducer.
for example:-
lets say i have added another reducer called universitydetails
then my mapstatetoprops will look something like this
const { results } = state.results || state.universitydetails
const studentData = state.results.studentData || state.universitydetails.studentData
return {
results,
studentData,
}
then again, adding condition at each prop level (given that i am using 15-20 different state variables in each of the three components)
Approach 3:-
add redux-saga and dispatch action on myuniversity load and yield other actions based on it
But this wont be generic. Incase, I want to add simliar feature for other things such as hostel then again i need to add sagas for this hostlels and need to dispatch action on initial load.
What I think will be best approach (correct me if am wrong) :-
Define a new reducer and somehow make my component listens to this reducer(like approach 2 but without uglifying the code) so that in case i want to add another feature like hostelselector, i just need to update my new reducer structure and make my component listen to this new reducer
I am stuck at somehow
Any suggestion how to go about this?
OK I think I understood how's the flow of your application and that's my idea.
You can create one reducer that will respond to just one action called 'SET_SOURCE'. There it will put the name of the current source where you should extract your data from. Ideally it should be the name of the reducer/object where your data will be hold.
After that you have to create a reducer for each source. Every source will be responsible of itself and they won't interact each other. That means that when your ajax call will be finished and you will have to save your data inside inside the store (aka firing an action), you will fire the action to trigger the reducer that you want.
The structure of your reducers could be like this:
=> myUniversity
=> currentSource
=> sources
=> uniDetails
=> hostel etc.
You can achieve this kind of structure using combineReducers function
// myUniversityReducer.js
import uniDetails from 'uniDetailsReducer'
import hostel from 'hostelReducer'
import currentSource from 'currentSourceReducer'
const sources = combineReducers({hostel, uniDetails})
const myUniversity = combineReducers({currentSource, sources})
export myUniversity
Inside your mapStateToProps you could something like to select the current source:
function mapDispatchToProps = (state) => {
const currentSource = selectCurrentSource(state) // currentSource = 'uniDetails' => name of the reducer
const dataFromSource = selectDataFromSources(state, currentSource) // dataFromSource = state[currentSource] => object inside uniDetails
// ... if here you need to manipulate data because the structure of every
// source is different, you can have your function that will do that based on the source name
return { ...dataFromSource }
}
That's my idea but there might be the chance that I missed something or I misunderstood some of the scenario

How to pass JSON to Redux -> React Component

I am building an app from this boilerplate: https://github.com/werein/react
I am hitting an API that requires a Secret Key, so I am going to Fetch the JSON via Express (server.js) and pass it to my component as props.
How do I glue everything together to get the JSON in as props?
I tried just to pass some Dummy JSON
app.get('yourinfo', (req, res) => {
res.json({ 'a': 'Some JSON' });
});
<ConnectedRouter history={history}>
<Layout>
<Match exactly pattern="/yourinfo" component={App} />
</Layout>
</ConnectedRouter>
And I don't get anything rendered in when I inspect this.props.
Any ideas?
Remember when using redux that your application's state is stored in the redux state. And all changes to the state are done through actions which are dispatched.
So, to save your API key in your application's state, just create an action & dispatch it:
// the "save API key" action
export function saveAPIKey(key) {
return {
type: APP_APIKEY,
key,
};
}
// to use the loadAPIKey action, dispatch it
import store from './store/store';
import { saveAPIKey } from './path/to/actions';
fetch('/my/api/endpoint')
.then((response) => response.json())
.then((data) => store.dispatch(saveAPIKey(data.key)));
That's the basic idea, but there is a simpler way of doing it. If you read through the redux docs on async actions you'll see that you can use the redux-thunk package to create an action that behaves asynchronously. It looks a little more complicated, but it has the advantage of putting all the code handling the asynchronous actions in one place, rather than spreading fetch calls throughout your code.

How to hydrate server-side parameters with React + Redux

I have a universal React app that is using Redux and React Router. Some of my routes include parameters that, on the client, will trigger an AJAX request to hydrate the data for display. On the server, these requests could be fulfilled synchronously, and rendered on the first request.
The problem I'm running into is this: By the time any lifecycle method (e.g. componentWillMount) is called on a routed component, it's too late to dispatch a Redux action that will be reflected in the first render.
Here is a simplified view of my server-side rendering code:
routes.js
export default getRoutes (store) {
return (
<Route path='/' component={App}>
<Route path='foo' component={FooLayout}>
<Route path='view/:id' component={FooViewContainer} />
</Route>
</Route>
)
}
server.js
let store = configureStore()
let routes = getRoutes()
let history = createMemoryHistory(req.path)
let location = req.originalUrl
match({ history, routes, location }, (err, redirectLocation, renderProps) => {
if (redirectLocation) {
// redirect
} else if (err) {
// 500
} else if (!renderProps) {
// 404
} else {
let bodyMarkup = ReactDOMServer.renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>)
res.status(200).send('<!DOCTYPE html>' +
ReactDOMServer.renderToStaticMarkup(<Html body={bodyMarkup} />))
}
})
When the FooViewContainer component is constructed on the server, its props for the first render will already be fixed. Any action I dispatch to the store will not be reflected in the first call to render(), which means that they won't be reflected in what's delivered on the page request.
The id parameter that React Router passes along isn't, by itself, useful for that first render. I need to synchronously hydrate that value into a proper object. Where should I put this hydration?
One solution would be to put it, inline, inside the render() method, for instances where it's invoked on the server. This seems obviously incorrect to me because 1) it semantically makes no sense, and 2) whatever data it collects wouldn't be properly dispatched to the store.
Another solution which I have seen is to add a static fetchData method to each of the container components in the Router chain. e.g. something like this:
FooViewContainer.js
class FooViewContainer extends React.Component {
static fetchData (query, params, store, history) {
store.dispatch(hydrateFoo(loadFooByIdSync(params.id)))
}
...
}
server.js
let { query, params } = renderProps
renderProps.components.forEach(comp =>
if (comp.WrappedComponent && comp.WrappedComponent.fetchData) {
comp.WrappedComponent.fetchData(query, params, store, history)
}
})
I feel there must be better approach than this. Not only does it seem to be fairly inelegant (is .WrappedComponent a dependable interface?), but it also doesn't work with higher-order components. If any of the routed component classes is wrapped by anything other than connect() this will stop working.
What am I missing here?
I recently wrote an article around this requirement, but it does require the use of redux-sagas. It does pickup from the point of view of redux-thunks and using this static fetchData/need pattern.
https://medium.com/#navgarcha7891/react-server-side-rendering-with-simple-redux-store-hydration-9f77ab66900a
I think this saga approach is far more cleaner and simpler to reason about but that might just be my opinion :)
There doesn't appear to be a more idiomatic way to do this than the fetchData approach I included in my original question. Although it still seems inelegant to me, it has fewer problems than I initially realized:
.WrappedComponent is a stable interface, but the reference isn't needed anyway. The Redux connect function automatically hoists any static methods from the original class into its wrapper.
Any other higher-order component that wraps a Redux-bound container also needs to hoist (or pass through) any static methods.
There may be other considerations I am not seeing, but I've settled on a helper method like this in my server.js file:
function prefetchComponentData (renderProps, store) {
let { params, components, location } = renderProps
components.forEach(componentClass => {
if (componentClass && typeof componentClass.prefetchData === 'function') {
componentClass.prefetchData({ store, params, location })
}
})
}

Firing Redux actions in response to route transitions in React Router

I am using react-router and redux in my latest app and I'm facing a couple of issues relating to state changes required based on the current url params and queries.
Basically I have a component that needs to update it's state every time the url changes. State is being passed in through props by redux with the decorator like so
#connect(state => ({
campaigngroups: state.jobresults.campaigngroups,
error: state.jobresults.error,
loading: state.jobresults.loading
}))
At the moment I am using the componentWillReceiveProps lifecycle method to respond to the url changes coming from react-router since react-router will pass new props to the handler when the url changes in this.props.params and this.props.query - the main issue with this approach is that I am firing an action in this method to update the state - which then goes and passes new props the component which will trigger the same lifecycle method again - so basically creating an endless loop, currently I am setting a state variable to stop this from happening.
componentWillReceiveProps(nextProps) {
if (this.state.shouldupdate) {
let { slug } = nextProps.params;
let { citizenships, discipline, workright, location } = nextProps.query;
const params = { slug, discipline, workright, location };
let filters = this._getFilters(params);
// set the state accroding to the filters in the url
this._setState(params);
// trigger the action to refill the stores
this.actions.loadCampaignGroups(filters);
}
}
Is there a standard approach to trigger actions base on route transitions OR can I have the state of the store directly connected to the state of the component instead of passing it in through props? I have tried to use willTransitionTo static method but I don't have access to the this.props.dispatch there.
Alright I eventually found an answer on the redux's github page so will post it here. Hope it saves somebody some pain.
#deowk There are two parts to this problem, I'd say. The first is that componentWillReceiveProps() is not an ideal way for responding to state changes — mostly because it forces you to think imperatively, instead of reactively like we do with Redux. The solution is to store your current router information (location, params, query) inside your store. Then all your state is in the same place, and you can subscribe to it using the same Redux API as the rest of your data.
The trick is to create an action type that fires whenever the router location changes. This is easy in the upcoming 1.0 version of React Router:
// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));
Now your store state will always be in sync with the router state. That fixes the need to manually react to query param changes and setState() in your component above — just use Redux's Connector.
<Connector select={state => ({ filter: getFilters(store.router.params) })} />
The second part of the problem is you need a way to react to Redux state changes outside of the view layer, say to fire an action in response to a route change. You can continue to use componentWillReceiveProps for simple cases like the one you describe, if you wish.
For anything more complicated, though, I recommending using RxJS if you're open to it. This is exactly what observables are designed for — reactive data flow.
To do this in Redux, first create an observable sequence of store states. You can do this using rx's observableFromStore().
EDIT AS SUGGESTED BY CNP
import { Observable } from 'rx'
function observableFromStore(store) {
return Observable.create(observer =>
store.subscribe(() => observer.onNext(store.getState()))
)
}
Then it's just a matter of using observable operators to subscribe to specific state changes. Here's an example of re-directing from a login page after a successful login:
const didLogin$ = state$
.distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
.filter(state => state.loggedIn && state.router.path === '/login');
didLogin$.subscribe({
router.transitionTo('/success');
});
This implementation is much simpler than the same functionality using imperative patterns like componentDidReceiveProps().
As mentioned before, the solution has two parts:
1) Link the routing information to the state
For that, all you have to do is to setup react-router-redux. Follow the instructions and you'll be fine.
After everything is set, you should have a routing state, like this:
2) Observe routing changes and trigger your actions
Somewhere in your code you should have something like this now:
// find this piece of code
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
// we need to bind the observer to the store <<here>>
}
What you want to do is to observe changes in the store, so you can dispatch actions when something changes.
As #deowk mentioned, you can use rx, or you can write your own observer:
reduxStoreObserver.js
var currentValue;
/**
* Observes changes in the Redux store and calls onChange when the state changes
* #param store The Redux store
* #param selector A function that should return what you are observing. Example: (state) => state.routing.locationBeforeTransitions;
* #param onChange A function called when the observable state changed. Params are store, previousValue and currentValue
*/
export default function observe(store, selector, onChange) {
if (!store) throw Error('\'store\' should be truthy');
if (!selector) throw Error('\'selector\' should be truthy');
store.subscribe(() => {
let previousValue = currentValue;
try {
currentValue = selector(store.getState());
}
catch(ex) {
// the selector could not get the value. Maybe because of a null reference. Let's assume undefined
currentValue = undefined;
}
if (previousValue !== currentValue) {
onChange(store, previousValue, currentValue);
}
});
}
Now, all you have to do is to use the reduxStoreObserver.js we just wrote to observe changes:
import observe from './reduxStoreObserver.js';
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
observe(store,
//if THIS changes, we the CALLBACK will be called
state => state.routing.locationBeforeTransitions.search,
(store, previousValue, currentValue) => console.log('Some property changed from ', previousValue, 'to', currentValue)
);
}
The above code makes our function to be called every time locationBeforeTransitions.search changes in the state (as a result of the user navigating). If you want, you can observe que query string and so forth.
If you want to trigger an action as a result of routing changes, all you have to do is store.dispatch(yourAction) inside the handler.

Resources