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.
Related
I have here a react stateless function that I would love to call my mutations from my reducer. Note, calling the mutation inside a React.Component Class works by means of adding a function inside the component, I dont want that, I want to use my reducer to do that.
With minimum code example, I'll show my setup
// index.js render function
[...] // imported variables
<ApolloProvider client={client}>
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<App />
</div>
</ConnectedRouter>
</Provider>
</ApolloProvider>
// Mutation
const Foo = gql`
mutation Foo($id: String) {
AddBar(id: $id) {
id
}
}
`;
// Component
[...] // other imports for below variables
const Comp = (props) => (
<div>
<button onClick={() => props.addFoo(props)}>Click</button>
</div>
)
const mapDispatchToProps = dispatch => bindActionCreators({
addFoo
}, dispatch)
export default connect(
mapDispatchToProps
)(graphql(Foo)(Comp))
// Reducer (very slimmed with just the action)
export const addFoo = (props) => {
// props contained the mutate function
props.mutate({
variables: {id: "1" }
})
// dispatch goes here but not needed.
}
Ok, I have slimmed down this example as much as possible. My issue is that my variables are not passing to my mutation function. If I hard-code id with one and click the button, graphql changes my data (yes) but the only issue is variables are not passing. In the inspector I do see the variables with correct values but...not passing to the mutate function.
A few thoughts...
First, reducers should never mutate data or make asynchronous calls. They are meant to be pure functions without side effects.
Taking GraphQL out of the picture for a moment and assuming that you have just a REST call, you'd usually mutate inside of an Action Creator or something similar. Action Creators in Redux are inherently synchronous, so you'd employ either Redux Thunks or Redux Sagas (or something similar) to help with that.
Ok, let's put GraphQL back in. As you pointed out, if you include your mutation in your component, it is difficult to wire that in to your Redux implementation. The two are kind of mutually exclusive. In your Redux implementation, however you'd normally make async fetch calls, you can use the Apollo Client without React to mutate...
const apolloClient = createApolloClient();
apolloClient.mutate({mutation: Foo}).then(handleResult)
Now, what does createApolloClient() do? You don't want to create a new one every time. That client maintains a cache and can handle all of the value-add which comes from re-using the client. That goes for the React code. You'd like for any queries to be executed from the React bindings to use the same client which you use in your Redux action creators. That createApolloClient() function needs to create a client singleton and return it so that you'd use it in your ApolloProvider as well:
<ApolloProvider client={createApolloClient()}>
I hope this helps!
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
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 ;)
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 })
}
})
}
After reading many questions regarding this topic I am still unsure as to which is the best way to asynchronously fetch data which later will be passed down as props to the child routes with React Router v1.0.0 and up.
My route config looks something like this:
import { render } from 'react-dom';
// more imports ...
...
render(
<Router>
<Route path="/" component={App} />
<IndexRoute component={Dashboard}/>
<Route path="userpanel" component={UserPanel}/>
</Router>,
document.getElementById('container')
)
In my App component I have code which asynchronously fetches data from the backend and will incorporate it into its state, if fetching was successful. I use componentDidMount for this within App.
The state of App will look like this contrived example:
{
user: {
name: 'Mike Smith',
email: 'mike#smith.com'
}
}
I would want to pass the user part of state as props to my IndexRoute and the userpanel route. However I am not sure how I should do this.
A few questions come to mind:
Should I place the async data request somewhere else within my code?
Should I use the React Router api (like onEnter) instead of React lifecycle methods for the data fetching?
How can I pass the state (user) of App to the Dashboard and UserPanel components as props?
I am unsure how to do this with React.cloneElement as seen in other answers.
Thanks for the help in advance.
What you are asking for is persistent data between routes and that's not the job of the router.
You should create a store (in flux terms), or a model/collection (in MVC terms) - the usual approach with react is something flux-like. I recommend redux.
In the redux docs it has an example of fetching a reddit user:
componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
}
Personally I don't think flux/redux is the easiest approach to implement, but it scales well. The essential concept is even if you decide to use something else:
You are correct, as Facebook suggests, async fetching goes best in componentDidMount.
If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.
Next you need to set this data in a store/model which can be accessed from other components.
The nice thing about redux (with react-redux) is that for each component you can say "Here are the actions this component is interested in" and then that component can simply call the action like UserActions.fetchUserIfNeeded() and the action will figure out whether it already has the user or if it should be fetched, and afterwards it will re-render and the prop will be available.
Answer to Q4: What are you trying to clone and why? If it's a child see this answer.
You can do one thing when your application start at that time you will call the API and fetch the data and register your Route like
my index.js is entry file then
here I have used React-Router 0.13.3 you can change the syntax as per new Router
fetchData(config.url+'/Tasks.json?TenantId='+config.TenantId).then(function(items)
{
var TaskData=JSON.parse(JSON.stringify(items.json.Tasks));
var Data=[];
Object.keys(TaskData).map(function(task){
if(TaskData[task].PageName !=='' && TaskData[task].PageUrl !=='')
{
Data.push({PageName:TaskData[task].PageName,path:TaskData[task].PageName+'/?:RelationId',PageUrl:TaskData[task].PageUrl});
}
});
Data.push({PageName:'ContainerPage',path:'/ContainerPage/?:RelationId',PageUrl:'./pages/ContainerPage'});
var routes=require('./routes')(Data);
$("#root").empty();
Router.run(routes,function(Handler){
React.render(<Handler />,document.getElementById('root'));
});
React.render(<UserRoles />, document.getElementById("userrole"));
}).catch(function(response)
{
showError(response);
});
I have pass the data to routes.js file like var routes=require('./routes')(Data); and my routes.js file look like
export default (data =>
<Route name="App" path="/" handler={App}>
<NotFoundRoute handler={require('./pages/PageNotFound')} />
<Route handler={TaskList} data={data} >
</Route>
{ data.map(task =>
<Route name={task.PageName} path={task.path} handler={require(task.PageUrl)}>
</Route>
) }
</Route>
);
I am not entirely sure I understand the question, but I just recently passed properties to the children of my routes as well. Pardon me if this is not the best way of doing it, but you'll have to clone your children and edit them and then pass down the copies not the children. I'm not sure why react and the react router make you do this, but try this:
let children (or whatever you want to name it) = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {name of property: property value});
});
Afterwards, you should be able to access those properties in this.props in the sub routes. Please ask if you have any questions because this is pretty confusing.