Reactjs render and state change dependancies - javascript

I'm learning ReactJS at the moment and struggling to understand how to render/update content based on changes elsewhere.
Example:
I have a timer app, which includes pause/restart functionality. It contains a Start/Pause button.
Timer.js
export class Timer {
constructor(parentApp) {
this.app = app;
this.playing = false;
}
start() {
this.playing = true;
}
pause() {
this.playing = false;
}
}
Button.js
export class IconButtonBar extends Component {
constructor(props) {
super(props);
this.state = {label: 'Start'};
}
render() {
return (
<div className="IconButtonBar">
<Button label={this.state.label} />
</div>
);
}
}
I want to update the label:
Depending whether the timer is started/stopped at initial render
When the timer is manually started/stopped by pressing the button
When the timer is manually started/stopped by another means
When the timer is programatically started/stopped (e.g. limit reached)
In jQuery I'd probably fire a custom event trgger to the body tag:
$('body').on('start_playing', function() {
$('#playpause_button).text('Pause');
}
But there are probably much more 'native' ways to do this in ReactJS.
I hope this makes sense, and you can help!

You need to change your vision of how react app is structurized. Just forget about jQuery. React is declarative, you need to say it what should be rendered (surely it can be done another way, but why to use React then?).
You can use stateful component if you have no third-party store library.
You can use conditional redering for showing different elements depending on different state.
Here is an example stateful component of what you wanted to achieve: https://codesandbox.io/s/14vokm9q14
Just remember, it is not a good practice to keep state in a view layer. Consider using Redux/Mobx:
Redux - it will require a lot of boilerplate: creation of reducers, action-creators, action-types, handling side-effects (with redux-thunk/redux-saga/redux-observable), etc. But it is stable, reliable and easy-testable.
Mobx - something like you have in your example. It is MVVM, where you have model in its classical meaning, decorate properties as observable and just inject this model into your react component. After that you can just mutate properties of your model and these changes will reflect onto your view.

Related

Storing methods needed in all components

I have a universal app I'm developing for learning purposes. I'm managing the state of my app with Redux, so all my data will be available there. But I want to create some methods that I'm going to use in all my components. The problem is: where should I store this methods?
Adding them to a parent component and passing the methods as props doesn't seem very useful, because this is one of the things that Redux tries to solve. And I'm pretty sure that Redux is not a place for storing methods.
I know I can create a class in a file somewhere, export it, add some methods to it, and when I want to use one method in a component I can call this file, create an instance of the class and call the needed method; but this doesn't look very react to me…
Is there a right way to create methods available for all components?
I've had some success sharing functions between components using an approach similar to the following. I'm not sure this approach will solve your specific use case with regards to cookies, however.
These functions can be stored anywhere and imported wherever required. They accept a component as their first argument, then return a function that operates on the component passed in.
Indicative, untested code follows.
// An event handler than can be shared between multiple components
const handleChange = component => event => component.setState({ value: event.target.value });
class ComponentOne extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}
class ComponentTwo extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}

ReactJS - Lifting state up vs keeping a local state

At my company we're migrating the front-end of a web application to ReactJS.
We are working with create-react-app (updated to v16), without Redux.
Now I'm stuck on a page which structure can be simplified by the following image:
The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount() method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer receives as prop state.allData.left from MainContainer and passes props.left.data to SearchableList, once again as prop.
RightContainer receives as prop state.allData.right from MainContainer and passes props.right.data to SelectableList and props.right.pins to Map.
SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.
I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render() is called in both components.
It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.
As feasible alternative I thought of adding a _selected property to each item in state.right.data in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate() to avoid useless render() especially in LeftContainer.
Which is / could be the best solution to optimise this page from an architectural and performance point of view?
Below you have an extract of my components to help you understand the situation.
MainContainer.js
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
allData: {}
};
}
componentDidMount() {
fetch( ... )
.then((res) => {
this.setState({
allData: res
});
});
}
render() {
return (
<div className="main-container">
<LeftContainer left={state.allData.left} />
<RightContainer right={state.allData.right} />
</div>
);
}
}
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [ ... ]
};
}
onDataSelection(e) {
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
}
render() {
return (
<div className="main-container">
<SelectableList
data={props.right.data}
onDataSelection={e => this.onDataSelection(e)}
selectedItems={this.state.selectedItems}
/>
<Map
pins={props.right.pins}
selectedItems={this.state.selectedItems}
/>
</div>
);
}
}
export default RightContainer;
Thanks in advance!
As React docs state
Often, several components need to reflect the same changing data. We
recommend lifting the shared state up to their closest common
ancestor.
There should be a single “source of truth” for any data that changes
in a React application. Usually, the state is first added to the
component that needs it for rendering. Then, if other components also
need it, you can lift it up to their closest common ancestor. Instead
of trying to sync the state between different components, you should
rely on the top-down data flow.
Lifting state involves writing more “boilerplate” code than two-way
binding approaches, but as a benefit, it takes less work to find and
isolate bugs. Since any state “lives” in some component and that
component alone can change it, the surface area for bugs is greatly
reduced. Additionally, you can implement any custom logic to reject or
transform user input.
So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems as a state in the RightContainer is completely justified and a good approach, since the parent doesn't need to know about and this data is being shared by the two child components of RightContainer and those two now have a single source of truth.
As per your question:
As feasible alternative I thought of adding a _selected property to
each item in state.right.data in MainContainer and pass the select
callback three levels down to SelectableList, handling all the
possible actions in MainContainer
I wouldn't agree that this is a better approach than the first one, since you MainContainer doesn't need to know the selectedItems or handler any of the updates. MainContainer isn't doing anything about those states and is just passing it down.
Consider to optimise on performance, you yourself talk about implementing a shouldComponentUpdate, but you can avoid that by creating your components by extending React.PureComponent which essentially implements the shouldComponentUpdate with a shallow comparison of state and props.
According to the docs:
If your React component’s render() function renders the same result
given the same props and state, you can use React.PureComponent for a
performance boost in some cases.
However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.
For example consider the following case
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render prop. However it would easily be done by connecting both of these components to Redux state using a connect function like
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
and similarly for MyComp. Also its easy to configure actions for updating relevant informations
Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well
My honest advice on this. From experience is:
Redux is simple. It's easy to understand and scale BUT you should use Redux for some specific use cases.
Since Redux encapsulates your App you can think of storing stuff like:
current app locale
current authenticated user
current token from somewhere
Stuff that you would need on a global scale. react-redux even allows for a #connect decorator on components. So like:
#connect(state => ({
locale: state.locale,
currentUser: state.currentUser
}))
class App extends React.Component
Those are all passed down as props and connect can be used anywhere on the App. Although I recommend just passing down the global props with the spread operator
<Navbar {...this.props} />
All other components (or "pages") inside your app can do their own encapsulated state. For example the Users page can do it's own thing.
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingUsers: false,
users: [],
};
}
......
You would access locale and currentUser through props because they were passed down from the Container components.
This approach I've done it multiple times and it works.
But, since you wanted to really consolidate the knowledge of React first, before doing Redux you can just store your state on the top-level component and pass it down to the children.
Downsides:
You're gonna have to keep passing them down into inner level components
To update state from the inner level components you're gonna have to pass the function that updates the state.
These downsides are a little boring and cumbersome to manage. That's why Redux was built.
Hope I helped. good luck
By using Redux you can avoid such callbacks and maintain the whole state in one single store - so make your parent component connected component - and make left and right components dumb ones - and just pass in the props you get from parent to child - and you don't have to worry about callbacks in this case.

How do I do a simple onClick to change a child component's classname with React Redux?

I have React Redux working to change my child component's classname, but I do it via
//Subscribe with our function that sets our state
this.props.store.subscribe( this.onSelectChange );
(where the onSelectChange is a function in my component that changes a property on its state.
According to the redux docs, I should instead be using "a view binding library" like ReactRedux's connect method. But every tutorial is incredibly complex. They're hard to understand and appear to be about 5 times more code than what I need to use right now. Using Redux directly, I have about 6 lines of total code.
How can i simply use the "proper" way to make my child component change its classname?
If really all you need is to update a classname on click, React is perfectly capable of doing this without involving the Redux store.
The whole idea with React is that each component has some state object, and a render function to turn the state into markup. If you want to change your view, you should change the state and let React call render again. Take the following example which toggles the classname of a button (not tested):
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
highlighted: false
}
this.buttonClicked = this.buttonClicked.bind(this);
}
buttonClicked() {
var highlighted = !this.state.highlighted
this.setState({
highlighted: highlighted
})
}
render(){
var classname = this.state.highlighted ? "highlighted-btn" : "normal-btn"
return (
<button onClick={this.buttonClicked} className={classname} />
)
}
}
We trigger render by calling setState, in which we use the state to determine the classname for the button.
solution 1
well, if you change className because you want different style.
I will suggest you can use setState in its childComponent instead.
here is for your reference
solution 2
on the other hand, if you want to use Redux to achieve that.
you probably need to have a reducer first. let's say selectState
and then you need an action. here we name it changeSelectState
now, we can use connect from react-redux on the container component
and pass this method down to that presentational component. that's it.
So, the flow you can do is
add a reducer for storing data
add an action for changing the data
import that data and action via connect into container component
pass them down to presentational component
click and change the value via that action

Stop loading a new route/component if current component has changes

I have a requirement where I need to check if the local state has changes before the user navigates to the next tab. sort of like handing a component abandonment. I have come up with 2 options as follows,
Achieve this via componentWillUnmount. If its a good practice is there a way to conditionally stop the component being unmounted?
Via the window. As stated in the following solution : https://groups.google.com/forum/#!topic/reactjs/z63RGG1l_0U
Any idea on this matter is greatly appreciated :)
In the react-router-redux router is part of the state, so you can experiment on that.
To do so you should take a look at RouterContext, which provides setRouteLeaveHook function.
OR
As far as I remember there's also second option. Router object on context contains listenBeforeLeavingRoute
this.context.router.listenBeforeLeavingRoute
But its basically the same thing if you look at the source code of react-router. But its accessible from different layers.
EDIT:also Route has onLeave hook, may be useful!
Hope it helps somehow.
Regards,
Mariusz
How does the user navigate to the next tab? When you are using a <Link> you could define an unsavedChanges flag in your state. Set this to true (via dispatching an action and having a reducer responsible for that action) whenever you think that the user must not leave the current tab.
class Foo extends React.Component {
handleClick(e) {
const { unsavedChanges } = this.props
if(unsavedChanges) {
e.preventDefault()
}
}
render() {
return (
<Link to='/nextTab' onClick={this.handleClick}>Bar</Link>
)
}
}
Of course you need to pass unsavedChanges to your components props.

Advice about webapp architecture and reactJs

This question is more to know your opinions about the way I'm trying to solve this issue.
I would need a little bit of ReactJs expertise here as I'm quite new.
First a little bit of context. I'm developing a web application using ReactJs for the frontend part.
This webapp is going to have many translations, so for maintenance I thought it would be better to store all the translations in a database instead of having them into a file. This way I could manage them using sql scripts.
I'm using a MySQL database for the backend, but for performance reasons, I have added ElasticSearch as second database (well, it is more a full text search engine).
So once the application starts, the translations are automatically loaded into ElasticSearch. Every translation has a code, and a text, so in elastic search I only load the translations for one locale (by default english), and when a user switchs the locale, a call is done to load all the translations for the selected locale and update their corresponding text.
This way from the fronted I can reference a translation only by the code and I will get the text translated in the correct locale.
Now, how do I do that in react?
So far I have written a component TranslatedMessage which is basically looking for a given code and displaying it whereever this component is rendered.
Below the code of the component:
import React from 'react';
export class TranslatedMessage extends React.Component {
constructor() {
super();
this.render = this.render.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.state = {message: ''};
}
render() {
return (<div>{this.state.message}</div>);
}
componentDidMount() {
var component = this;
var code=this.props.code;
var url="data/translation?code="+code;
$.get(url, function (result) {
component.setState({message: result.text});
});
}
};
And then I use it in the application whis way, for example to translate the title of an 'a' link:
<TranslatedMessage code="lng.dropdown.home"/><i className="fa fa-chevron-down" />
So far is working fine but the problem is that I need to refresh the whole page to get the new translations displayed, because I'm not updating the state of the component.
So now my questions:
1)Every time that we find in a page the component TranslatedMessage, a new instance of that component is created right? so basically if I have 1000 translations, 1000 instances of that component will be created? And then React has to take care and watch all these instances for changes in the state? Would that be very bad for performance? Do you find any more efficient way to do it?
2) I don't think forcing the whole page to reload is the most proper way to do it, but how can I update the states of all that components when a user switch the locale? I've been reading about a framework (or pattern) called Flux, and maybe that could fit my needs, what do you thing, would you recommend it?
3) What do you think about storing translations on db, I'm a bit concern about sending a query to the db for every translation, would you recommend or not this approach?
Any suggestions, ideas for improvement are very welcome!
Thank you for taking your time to read it or for any help!
I use what is basically a Flux store for this purpose. On initialisation the application requests the whole language file to use (which is JSON) and that gets shoved into memory in the store, something like this (I'm going to assume a totally flat language file for now, and I'm using ES2015/16 syntax, I've omitted error checking etc etc for brevity):
class I18n {
constructor() {
this.lang = await fetch( 'lang_endpoint' )
.then( res => res.json() )
}
get( translation ) {
return this.lang[ translation ] || null
}
}
Somewhere my app starts during a ReactDOM.render( <App /> ) or some variation and this renders the whole thing top-down (I try to eliminate state as much as possible). If I needed to switch languages then I'd bind a change handler such that the store emits a change event which is heard by some code that triggers a ReactDOM.render. This is fairly standard Flux practise for changing the app state, the key is to try and eliminate state from your components and store it inside your stores.
To use the I18n class simply instantiate it somewhere (I normally have it as a singleton exported from a file, e.g. module.exports = new I18n(), require that file into your components and use the get method (this assumes some sort of packager such as browserify or webpack but it looks like you have that complexity all sorted):
import 'i18n' from 'stores/i18n'
class MyComponent extends React.Component {
constructor() { ... }
render() {
return (
<span>{ i18n.get( 'title' ) }</span>
)
}
}
This component could also be simplified to
const MyComponent = props => <span>{ i18n.get( 'title' ) }</span>

Categories

Resources