How to pass props to react-router 1.0 components? - reactjs

Please note, that although the question itself is largely a duplicate of this, it concerns a different version which should support this. The linked question already accepted an answer on an old version
I'm pretty confused about what the intended workflow is.
Let's say I have a menu system where clicking on each item uses react-router to navigate to an area which pulls some data from the server.
url: yoursite/#/lists/countries
----------------------------
Billing Codes | <<Countries>> | Inventory Types
---------------------------------------------------------
Countries:
---------------
Afghanistan
Azerbaijan
Belarus
with routes something like
Route #/lists component: Lists
Route billing-codes component: BillingCodes
Route countries component: Countries
Route inventory-types component: InventoryTypes
I don't want to preload data from the server until an area is navigated to, so in my Countries component's on componentWillMount I fire an event (I'm using reflux but...whatever) that triggers a store to do an ajax request and update itself with the current list of countries.
Now the Countries component reacts to that change in state by updating the countries in its props. Except - reasonably - that generates an invariant error because I shouldn't be updating props on a child component, I should update it at the top level. But the top level is the router itself so now I'm just lost - where am I supposed to listen to changes and update props from?
(Cross-posted to the issue tracker as I think it needs some clearer documentation)

Reading the react-router 0.13 -> 1.0 Upgrade Guide and this example led me to the following:
{ this.props.children &&
React.cloneElement(this.props.children, {newprop: this.state.someobject }) }
Instead of including the child components directly, we clone them and inject the new properties. (The guard handles the case where there is no child component to be rendered.) Now, when the child component renders, it can access the needed property at this.props.newprop.

The easy way is to just use this.state, but if you absolutely have to use this.props then you should probably extend Router.createElement.
First add the createElement prop to your Router render.
React.render(
<Router history={history} children={Routes} createElement={createElement} />,
document.getElementById('app')
);
Wrap all of your components in a new Container component.
function createElement(Component, props) {
return <Container component={Component} routerProps={props} />;
}
Your Container component will probably look something like this. Pass an onLoadData function down to your component.
import React from 'react';
import _ from 'lodash';
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { props: props.routerProps };
}
onLoadData(props) {
var mergedProps = _.merge(this.state.props, props);
this.setState({ props: mergedProps });
}
render() {
var Component = this.props.component;
return <Component {...this.state.props} onLoadData={this.onLoadData.bind(this)} />;
}
}
Then from your loaded component, when you get your data back from the server, just fire this.props.onLoadData(data) and the data will be merged with your current props.
Read the Router.createElement Documentation

Related

Struggling with this.props in nested components

Apologies if this is a basic question but I am new to react/gatsby and i am struggling to find an answer to my question as i am not sure the exact terminology.
I am currently building a site using atomic design principles. i want to update the copy for atom components such as buttons/forms when they are used around the site - however i am struggling to pass data using the methods i know of.
Code set up
Atom/Button components the text is coded as such
<button>{this.props.copy}</button>
Layout component such as a hero banner The button is imported in the layout using
<section>This is a hero banner <button copy="copy goes here" /></section>
Page component I want to use the layout/hero component across various pages, I've imported the layout component and overwrite the button text already defined in the layout component however using
<layout copy="overwrite the copy"> obviously will not work
Is there a way to either pull a component into another component as it is called in such as <hero <button copy="new copy"/> /> and overwrite the prop. OR a better way to define props in the atom components that they can be nested. so the structure looks like this (the third level components are always in layouts and rarely pulled into the page by themselves.)
Page 1
Page 2
├── Layout (Hero)
├─────── Atom (button)
├─────── Atom (Input)
└─────── Atom (Select)
any help would be greatly appreciated.
You have a couple options here:
Prop Drilling
Render Props (React docs).
I am going to say avoid Prop Drilling as much as possible. It becomes cumbersome on large component hierarchies. Try a more dynamic approach with the render props that allows you swap out implementations with out causing issues.
Prop Drilling:
This would involve passing props down the component hierarchy. For your use case, it would look like this
class Layout extends Component {
render() {
return (
<section>
This is a hero banner <AtomButton copy={this.props.copy} />
</section>
)
}
}
class AtomButton extends Component {
render() {
return (
<button>this.props.copy</button>
)
}
}
Render Props:
What I am showing here is not actually a render prop. This is just passing a component down as a prop and then rednering the component. A true render props is defined in the React docs I linked.
class Layout extends Component {
render() {
return (
<section> This is a hero banner {this.props.button} /><section>
)
}
}
//Wherever your are loading your Layout Component
class Page extends Component {
render() {
return (
<Layout buttonRender={<AtomButton copy="overwrite copy" />} />
)
}
}

React Context API is slow

I'm experimenting with new Context API and hooks. I've created an app with sidebar (treeview), footer and main content page. I have a context provider
const ContextProvider: FunctionComponent = (props) => {
const [selected, setSelected] = useState(undefined);
const [treeNodes, setTreeNodes] = useState([]);
return (
<MyContext.Provider
value={{
actions: {
setSelected,
setTreeNodes
},
selected,
treeNodes
}}
>
{props.children}
</MyContext.Provider>
);
Im my content component I have a DetailsList (Office Fabric UI) with about 1000 items. When I click on the item in the list I want to update selected item in context. This works but it is really slow. It takes about 0,5-1 seconds to select item in the list. The list is virtualized. I have tried it on production build. Thing are a bit better but there is a noticable lag when clicking on list.
Footer is consuming myContext to display information about selected item.
Here is a bit of code from my component
const cntx = useContext(MyContext);
const onClick = (item) => {
cntx.actions.setSelected(item);
};
Am I using the context wrong?
I've created a sample sandbox to demonstrate.. You can scroll to about 100-th index and click a couple of times to see how it gets unresponsive.
https://codesandbox.io/s/0m4nqxp4m0
Is this a problem with Fabric DetailsList? Does it reRender to many times? I believe the problem is with "complex" DatePicker component but I don't understand why does DetailsList get rerenderd? It's not using any of context properties within a render function. I would expect only Footer component to rerender on every context change
Caveats
Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
To get around this, lift the value into the parent’s state:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
https://reactjs.org/docs/context.html#caveats
Memorize your selected item with useMemo to avoid creating an new object if you are referring to the same item. Then pass it in a dedicated context
In your solution, whenever your component is rerendered, a new instance of value is passed down as a prop. This will trigger the re-rendering of the children as well.
If you use hooks, to prevent this, memoize the value object that you want to pass, in the useMemo or useCallback hook. In fact, this should be applied as a basic React practice to any of your components, not just a context component. Unless you're passing a primitive value (string, number, ...), don't create an instance or an inline function directly and pass it as a prop. Make sure the props you're passing don't get changed after each rendering cycle of the React component.

Force remounting component when React router params changing?

I've written a simple app where the remote resources are fetched inside componentDidMount functions of the components.
I'm using React Router and when the route changes completely, the previous component is unmounted well then the new one is mounted.
The issue is when the user is staying on the same route, but only some params are changed. In that case, the component is only updated. This is the default behaviour.
But it's sometimes difficult to handle the update in all the children components where previously only componentDidMount was needed...
Is there a way to force the remounting of the component when the user is staying on the same route but some params are changing?
Thanks.
Do the following
the route shoul look like this.
<Route path='/movie/:movieId' component={Movie} />
When you go to /movie/:movieID
class Movie extends Component {
loadAllData = (movieID) => {
//if you load data
this.props.getMovieInfo(movieID);
this.props.getMovie_YOUTUBE(movieID);
this.props.getMovie_SIMILAR(movieID);
}
componentDidMount() {
this.loadAllData(this.props.match.params.movieId);
}
componentWillReceiveProps(nextProps){
if(nextProps.match.params.movieId !== this.props.match.params.movieId) {
console.log(nextProps);
this.loadAllData(nextProps.match.params.movieId);
}
}
render(){
return( ... stuff and <Link to={`/movie/${id}`} key={index}>...</Link>)
}
}

How to get a component to pass state to parent's sibling

I have components as shown below
Component1 --- Component3
|
Component2
|
Component4
Components 2 and 3 are children of 1, and Component 4 is the child of 2.
I need to pass data from Component4 which I am storing as its state to Component3 which will display it. I presume the way to do this is to pass the state to from Component4 up to Component2 which will further send the state to Component1 using callbacks and finally Component1 will pass the state down to Component3. Is this the best way to do this?
Store the data in the state highest common parent (Component 1). Create methods on component1 that manipulate this state and bind this to them in the constructor. Then pass the state down to component3.
class component1 extends React.Component {
constuctor(props) {
super(props);
this.stateChanger = this.stateChanger.bind(this)
this.state = {
foo: 'bar'
}
}
stateChanger(e) {
this.setState({ foo: 'baz' })
}
render() {
<Component3 foo={this.state.foo} />
<Component2 stateChanger={this.stateChanger} />
}
}
edit: pass the stateChanger function down to component4 like below:
class Component2 extends React.Component {
render() {
return (
<div>
<Component4 stateChanger={this.props.stateChanger} />
</div>
)
}
}
Then do what you will with it.
Here is an article you should check out!
Callbacks will work. Another common solution is to lift the state up.
Instead of storing the state in Component4, simply move the state to Component1 and pass them down as props to Component3 and 4.
Personally I don't like a long callback chain, lifting the state is usually feels better to me.
If you have a lot of state that need to be shared between components, you should start to consider a state management solution, like Redux or MobX etc.
Best and scalable solution for this problem is using Flux or Redux which helps in the bidirectional data flow. They have a store where the data is stored, and whenever we want we can access and modify the data which is available to all components.
Have a look onto Redux and you can find examples implemented using Redux here

Updating state in more than one component at a time

I have a listview component which consists of a number of child listitem components.
Each child listitem have a showSubMenu boolean state, which display a few extra buttons next to the list item.
This state should update in response to a user event, say, a click on the component DOM node.
childcomponent:
_handleClick() {
... mutate state
this.props.onClick() // call the onClick handler provided by the parent to update the state in parent
}
However, it feels somewhat wrong to update state like, as it mutates state in different places.
The other way i figured i could accomplish it was to call the this.props.onClick directly, and move the child state into the parent as a prop instead, and then do change the state there, and trickle it down as props.
Which, if any, of these approaches is idiomatic or preferable?
First of all, I think that the question's title doesn't describe very well what's your doubt. Is more an issue about where the state should go.
The theory of React says that you should put your state in the higher component that you can find for being the single source of truth for a set of components.
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy).
Either the common
owner or another component higher up in the hierarchy should own the
state.
If you can't find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
However, a Software Engineer at Facebook said:
We started with large top level components which pull all the data
needed for their children, and pass it down through props. This leads
to a lot of cruft and irrelevant code in the intermediate components.
What we settled on, for the most part, is components declaring and
fetching the data they need themselves...
Sure, is talking about data fetched from stores but what im traying to say is that in some cases the theory is not the best option.
In this case i would say that the showSubMenu state only have sense for the list item to show a couple of buttons so its a good option put that state in the child component. I say is a good option because is a simple solution for a simple problem, the other option that you propose means having something like this:
var GroceryList = React.createClass({
handleClick: function(i) {
console.log('You clicked: ' + this.props.items[i]);
},
render: function() {
return (
<div>
{this.props.items.map(function(item, i) {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
);
}, this)}
</div>
);
}
});
If, in a future, the list view has to get acknowledge of that state to show something for example, the state should be in the parent component.
However, i think it's a thin line and you can do wathever makes sense in your specific case, I have a very similar case in my app and it's a simple case so i put the state in the child. Tomorrow maybe i must change it and put the state in his parent.
With many components depending on same state and its mutation you will encounter two issues.
They are placed in component tree so far away that your state will have to be stored in a parent component very high up in the render tree.
Placing the state very high far away from children components you will have to pass them down through many components that should not be aware of this state.
THERE ARE TWO SOLUTIONS FOR THIS ISSUE!
Use React.createContext and user context provider to pass the data to child elements.
Use redux, and react-redux libraries to save your state in store and connect it to different components in your app. For your information react-redux library uses React.createContext methods under the hood.
EXAMPLES:
Create Context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
REDUX AND REACT-REDUX
import { connect } from 'react-redux'
const App = props => {
return <div>{props.user}</div>
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App)
For more information about redux and react-redux check out this link:
https://redux.js.org/recipes/writing-tests#connected-components

Resources