Saving state of a Switch component in a stack navigator - javascript

I am new to programming. I'm setting up some boolean switches to allow the user to select preferences. The code I have got so far seems to work as had many issues with state being stuck or not toggling at all. Now my issue is that when I back out of the screen I'm working on and go back in, it always goes back to "true" state, so switches are always on when I go onto the page.
Is there something wrong with my code that the state doesn't persist, or should I move the state to redux store? If so how would I do this?
Multiple web articles and some youtube tutorials, they all seem to work on a barebones single page "Hello World" app. Can't find any real world examples to work with.
class SettingsMarketingPreferences extends Component {
constructor(props) {
super(props);
this.state = {
switchone: true,
switchtwo: true,
switchthree: true
};
}
<Switch
onValueChange={value => this.setState({ switchone: value })}
value={this.state.switchone}/>
Ideally it would save the state of the switches, there are three I just decided to share how one was built to avoid repetition. Long term goal is to attach it to a ID token so we would be able to see who has opted in or out for our marketing information.

<ToggleSwitch
isOn={this.state.toggle1}
onColor="green"
offColor="gray"
size='medium'
onToggle={()=>this.togglechange1()}
/>

Related

Resetting redux state without breaking react components

Been working with redux in a complex react application.
I put some data in redux state and use this data to render a component.
Then, you want to change the page and I do need to reset some fields in the redux state. Those fields were used to render the previous component.
So, if I reset the state before going to the next page, the previous page rerenders and throws errors because data is missing (because of the reset). But I can't reset in the next page, because that page is reached in many flows so it can be difficult to manage when to reset and when not.
How problems like this are managed in react applications?
All the example are simplified to show the problem. in the actual application, there are many fields to reset.
so, page 1
redux state
{field: someValue}
component uses the field value
function myComponent(props) => {
return (props.field.someMappingOperation());
}
Now, when going to page 2, field should be null, so we reset it
redux state
{field: null}
the component above rerenders, throwing an error because of
props.field.someMappingOperation()
and field is null.
What can be done is to load the next page, so this component is not in the page and then reset the state. yet, this becomes very hard to manage, because you the are in page B (suppose clients list with the list saved in redux) and you want to see the details of a client you go to page C, when you press back you don't want to reset again the state. So you add conditions on the reset. But because there are many flows in the application, that page can be reached in many ways. Have conditions for each way is not the best solution I suppose.
Edit:
I would like to add that state reset was not required initially and components aren't designed for that. As the application grew and became enough complex it became necessary. I'm looking for a solution that does not require me to add props value checking in every and each component, like
{this.props.field && <ComponentThatUsesField />}
This is really a lot of work and it's bug-prone as I may miss some fields.
As the application has a lot of pages and manages a lot of different data, the store results to be big enough to not want to have clones of it.
You have to design your components in a more resilient way so they don't crash if their inputs are empty.
For example:
render() {
return (
{ this.props.field && <ComponentUsingField field={this.props.field} />}
)
}
That's a dummy example, but you get the point.
you can change your code like below:
function myComponent(props) => {
const {fields} = props;
return (fields && fields.someMappingOperation());
}
This will not throw any error and is a safe check
You don't need to use conditions or reset your state, your reducer can update it and serve it to your component. You can call an action in componentDidMount() and override the previous values so they will be available in your component. I assume this should be implied for each of your component since you are using a different field values for each.

UI does not render after state is changed

I have an app (project in Udacity) in React which display books on my shelves according to categories: Currently Reading, Want to Read and Read. Every time I change the category say from Want to Read to Currently Reading the book will move to the right category and in this case it would be Currently Reading. My code works on this one with no problem. However, you can also search from the vast library of books wherein you could move to your shelf, by default the category is None, although you could include the existing books in your shelf as part of being search (aside from the main library of books). Now, my problem is this, if I move from None to Want To Read category for example my UI does not change after I click the back button that brought me back to the main page (i.e. App.js). When I do however, change of category in the main, I have no problem. Also my function for updating the Book Shelf in App.js when called does not show any error in the console.
I have the following components:
App
|
|--BooksSearch
|--BooksList
| |--BookShelf
|--BooksSearchPage
|--BookShelf
The BooksList and BooksSearch displays the books and the search button respectively in the main page (i.e. App.js). The BooksSearchPage allows user to search books from the library to move into the shelves. The BookShelf displays the list of books whether they are in the shelves or in the library.
This is my App.js
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
toCamelShelf(Shelf) {
if (Shelf==="currentlyreading") return "currentlyReading"
if (Shelf==="wanttoread") return "wantToRead"
return Shelf
}
updateBookShelf = (mybook, shelf) => {
shelf=this.toCamelShelf(shelf)
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
render() {
return (
<div className="app">
{this.state.showSearchPage ? (
<Route path='/search' render={({history})=>(
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
/>
)} />
) : (
<Route exact path='/' render={()=>(
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
</div>
)} />
)}
</div>
)
}
}
export default App
And since the code is too long, I included my repo in Github. I am very new to ReactJS and have been debugging this problem for the last 3 days but to no avail.
I'm having a hard time understanding the app enough to know why exactly, but it sounds like its a state issue.
If you navigate away and come back, or click something and it doesn't update properly, the state isn't being updated at that moment (that event) or the state wasn't saved correctly right before that event.
As soon as you reproduce the problem event, ask yourself "what was the state right before I did this?" and "why is the state how it is now?"
Did you forget to update the state?
Is it getting the wrong state from somewhere?
Did you call this.setState({ something })?
Did you overwrite the state instead of adding to it?
Is there a missing state update?
On both pages, right before and right after, add in the render method: console.log(this.state) and if needed, console.log(this.props). I think you will see the problem if you look there. The question is how exactly did it get like that? Re-visit all your state updates.
If you navigate away and come back, where does it get that state from? Why is that data in there?
Remember, React is a state machine. State is an object that has a snapshot of data every time you look at it. It's like looking at a piece of paper with all your data on it. If you leave the room and come back and the data isn't there, what updated your state and made it go away? or why didn't it get added to your state? That mechanism there is causing your problem.
I see a few spots in your code to focus on:
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
and
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
and
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
also right up here:
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
One of them is acting too strongly or one of them isn't updating at the right time, or data is getting overwritten, I suspect.
The console.log() should be most helpful. If your data is missing. Make it show up there at that time and the problem will go away :) (P.S. that setState on componentDidMount looks a little suspect).

Preventing react-redux from re-rendering whole page when state changes

I am reading several articles about how to prevent react-redux from re-rendering the whole page, when only one little thing changes.
One article suggests that instead of wrapping all into one big container (as in figure 1 here) wrapping all into smaller containers (as in figure 2 here). If something changes in Container 2, only Component 2 and Component 3 are getting re-rendered. Component 1 would not re-render.
Figure1
Figure2
I have following questions:
If I wrap everything in smaller containers, I would need "several" global states, for each container one (as indicated with the pseudo-code on the bottom of the figure). Is that common practice?
If it is ok to have "several" global states and I would need in some property from Container1 in Container2, I would need to connect that with two global states. To me that feels like it could get messy very quick. Where does what come from?
When and where would I use the react method shouldComponentUpdate()? Using the Big Container approach how would I differ which Component should be rerendered?! If implemented in the Components, they would not be "dump" anymore, because they need to access the global state in order to decide whether to re-render or not. I would not be able to reuse Components because every Component has its own special case when to rerender and when not. I am not sure where and when to use shouldComponentUpdate()
Please note that I am pretty new to this and might have made wrong assumptions etc. I basically want to know how not to re-render the whole page, when only one thing needs to be updated. The results from asking google differ a lot.
Your second approach is the way to go, though your definition of a global state is a bit misleading.
Basically, you want to have exactly one "global state". This is what is referred to as "store". All components that need to receive parts of the store are connected to it using react-redux' connect function.
Now, connect(...) is actually a HOC which wraps your component and passes only defined parts of the store to it. This way, the component (and its' children) only re-render when its' defined props change.
Don't be afraid to use connect() more often. You just have to be careful what parts of the store you pass to the container and this is exactly where performance can become an issue.
This should answer your first question. The second one is a question of design. Design in terms of how your app and maybe also in terms of how your datasource is structured. As said before, you want to have a minimum of props passed to a component so it doesn't re-render when other parts of the store change.
For the third question, you first have to understand that 'dumb components' can, of course, receive props from their parent components/containers. Dumb just means that they don't get to decide whether a re-render should happen or not. Dumb components are there to present/display data and that's it.
Let's say you have a really simple store:
const store = {
posts: {
all: [],
isFetching: false,
err: {},
}
}
And you connect your container to it like this:
function mapStateToProps(store) {
return {
posts: store.posts.all,
isFetching: store.posts.isFetching,
err: store.posts.err,
};
}
#connect(mapStateToProps)
And this container has three dumb components it can use:
A posts component, which receives all posts and displays them using another dumb child (pseudoCode, you get the point):
function posts = (posts) => {
posts.map((post, id) => (
<otherDumbComponent post={post} key={id} />
));
}
One to display just a spinner while isFetching
One to display the error if there's one.
Now, if only isFetching has changed, only the second component will re-render and that's it. Oh, and shouldComponentUpdate() is something you probably don't want to use, because, well.. there are many good blog posts about it.

Mounting Components in React Native

I am relatively new to JS and RN and I am currently working with an app where I have bumped in to some major issues regarding my handling of Components.
I've tried to run through the following guide: https://facebook.github.io/react/docs/component-specs.html as well as https://facebook.github.io/react/docs/advanced-performance.html but the latter one flies a bit over my head.
However, as I understand: componentWillMount fires whatever piece of code that is within before the render function is executed, and componentWillUnmount erases whatever it sais to forget. Or how can I specify?
My specific problem lies within the fact that I have three functions, one main and within main I have compOne and compTwo, where the two latter are called in the main component when pressing on a certain sub-navigator. This means that I have three instances of getInitialState whereas compOne and compTwo defines basically the same stuff but calls different parts of the server (hence the code is very much the same).
Also this issue resurfaces sometimes when I go between different frames, and return again to my home screen.
In my Home screen I have it like this:
var Home = React.createClass({
getInitialState: function() {
return {
componentSelected: 'One',
userName: "Loading...",
friendFeed: 'Loading...',
loaded: false,
loadedlocal: false,
};
},
componentWillMount: function() {
Method.getFriendFeed(this.props.tokenSupreme)
.then((res) => this.setState({
friendFeed: JSON.parse(res).friendPosts,
loaded: true,
}))
.catch((error) => console.log(error))
.done();
Method.getLocalFeed(this.props.tokenSupreme, )
.then((res) => this.setState({
localFeed: JSON.parse(res).friendPosts,
loadedlocal: true,
}))
.catch((error) => console.log(error))
.done();
},
Where I pass this.state.friedFeed to be a this.props.friendData in one of two components and vice versa for the localFeed.
Picking it up in my CompOne:
var ComponentOne = React.createClass({
getInitialState: function() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
return {
dataSource: ds.cloneWithRows(this.props.friendData),
};
},
render: function() {
if (!this.props.loaded) {
return this.renderLoadingView();
} else {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
style={styles.card} />
)
}
},
Followed by the renderRow function etc and the compTwo function is basically identical.
But my question is: How should I go about to unmount the component? If it even is what I want? Another frequently but not consequently occuring issue is the error of null is not an object (evaluating 'prevComponentInstance._currentElement' with reference to the _updateRenderedComponent hence my belief that I should go about some different method in mounting, unmounting and updating my components, or am I wrong?
After some browsing ill add another question to this, which might be the main question... Is it even possible for a RN app to handle mutiple listviews and mulitple fetchers in mutilple scenes?
In most situations, you do not need to be concerned about unmounting the components. When a React component is no longer needed, React in general just forgets about it, including its contents, props, state, etc. componentWillUnmount is typically reserved for things that are in a global state that, once the component is forgotten about would cause problems if they still existed.
The documentation on the page you linked to mentions cleaning up timers as an example. In Javascript, if you set a timer via setTimeout() / setInterval(), those timers exist in the global space. Now imagine you had a component that set a timer to modify some element on screen or potentially try to interact with a component, let's say 30 seconds in the future. But then the user navigates away from the screen/component, and because it is no longer on screen React forgets about it. However, that timer is still running, and may cause errors if it fires and it can't interact with that now-trashed component. componentWillUnmount gives you a chance to clear out that timer so weird side effects don't occur when it fires to interact with elements that no longer exist.
In your case you probably don't have anything that needs cleanup, as far as I can tell. You might want to clarify your question because you don't say what the trouble behavior is that you're seeing, but note also that getInitialState is only called the first time a component is created, and won't get called if only props change. So if the friendData is changing but the component stays on the screen, you will need to update your ds via a componentWillReceiveProps.
To your last question, yes it is certainly possible for React to handle multiple ListViews/fetches/etc.

Is connect() in leaf-like components a sign of anti-pattern in react+redux?

Currently working on a react + redux project.
I'm also using normalizr to handle the data structure and reselect to gather the right data for the app components.
All seems to be working well.
I find myself in a situation where a leaf-like component needs data from the store, and thus I need to connect() the component to implement it.
As a simplified example, imagine the app is a book editing system with multiple users gathering feedback.
Book
Chapters
Chapter
Comments
Comments
Comments
At different levels of the app, users may contribute to the content and/or provide comments.
Consider I'm rendering a Chapter, it has content (and an author), and comments (each with their own content and author).
Currently I would connect() and reselect the chapter content based on the ID.
Because the database is normalised with normalizr, I'm really only getting the basic content fields of the chapter, and the user ID of the author.
To render the comments, I would use a connected component that can reselect the comments linked to the chapter, then render each comment component individually.
Again, because the database is normalised with normalizr, I really only get the basic content and the user ID of the comment author.
Now, to render something as simple as an author badge, I need to use another connected component to fetch the user details from the user ID I have (both when rendering the chapter author and for each individual comment author).
The component would be something simple like this:
#connect(
createSelector(
(state) => state.entities.get('users'),
(state,props) => props.id,
(users,id) => ( { user:users.get(id)})
)
)
class User extends Component {
render() {
const { user } = this.props
if (!user)
return null
return <div className='user'>
<Avatar name={`${user.first_name} ${user.last_name}`} size={24} round={true} />
</div>
}
}
User.propTypes = {
id : PropTypes.string.isRequired
}
export default User
And it seemingly works fine.
I've tried to do the opposite and de-normalise the data back at a higher level so that for example chapter data would embed the user data directly, rather than just the user ID, and pass it on directly to User – but that only seemed to just make really complicated selectors, and because my data is immutable, it just re-creates objects every time.
So, my question is, is having leaf-like component (like User above) connect() to the store to render a sign of anti-pattern?
Am I doing the right thing, or looking at this the wrong way?
I think your intuition is correct. Nothing wrong with connecting components at any level (including leaf nodes), as long as the API makes sense -- that is, given some props you can reason about the output of the component.
The notion of smart vs dumb components is a bit outdated. Rather, it is better to think about connected vs unconnected components. When considering whether you create a connected vs unconnected components, there are a few things to consider.
Module boundaries
If you divided your app into smaller modules, it is usually better to constrain their interactions to a small API surface. For example, say that users and comments are in separate modules, then I would say it makes more sense for <Comment> component to use a connected <User id={comment.userId}/> component rather than having it grab the user data out itself.
Single Responsibility Principle
A connected component that has too much responsibility is a code smell. For example, the <Comment> component's responsibility can be to grab comment data, and render it, and handle user interaction (with the comment) in the form of action dispatches. If it needs to handle grabbing user data, and handling interactions with user module, then it is doing too much. It is better to delegate related responsibilities to another connected component.
This is also known as the "fat-controller" problem.
Performance
By having a big connected component at the top that passes data down, it actually negatively impacts performance. This is because each state change will update the top-level reference, then each component will get re-rendered, and React will need to perform reconciliation for all the components.
Redux optimizes connected components by assuming they are pure (i.e. if prop references are the same, then skip re-render). If you connect the leaf nodes, then a change in state will only re-render affected leaf nodes -- skipping a lot of reconciliation. This can be seen in action here: https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js
Reuse and testability
The last thing I want to mention is reuse and testing. A connected component is not reusable if you need to 1) connect it to another part of the state atom, 2) pass in the data directly (e.g. I already have user data, so I just want a pure render). In the same token, connected components are harder to test because you need to setup their environment first before you can render them (e.g. create store, pass store to <Provider>, etc.).
This problem can be mitigated by exporting both connected and unconnected components in places where they make sense.
export const Comment = ({ comment }) => (
<p>
<User id={comment.userId}/>
{ comment.text }
</p>
)
export default connect((state, props) => ({
comment: state.comments[props.id]
}))(Comment)
// later on...
import Comment, { Comment as Unconnected } from './comment'
I agree with #Kevin He's answer that it's not really an anti-pattern, but there are usually better approaches that make your data flow easier to trace.
To accomplish what you're going for without connecting your leaf-like components, you can adjust your selectors to fetch more complete sets of data. For instance, for your <Chapter/> container component, you could use the following:
export const createChapterDataSelector = () => {
const chapterCommentsSelector = createSelector(
(state) => state.entities.get('comments'),
(state, props) => props.id,
(comments, chapterId) => comments.filter((comment) => comment.get('chapterID') === chapterId)
)
return createSelector(
(state, props) => state.entities.getIn(['chapters', props.id]),
(state) => state.entities.get('users'),
chapterCommentsSelector,
(chapter, users, chapterComments) => I.Map({
title: chapter.get('title'),
content: chapter.get('content')
author: users.get(chapter.get('author')),
comments: chapterComments.map((comment) => I.Map({
content: comment.get('content')
author: users.get(comment.get('author'))
}))
})
)
}
This example uses a function that returns a selector specifically for a given Chapter ID so that each <Chapter /> component gets its own memoized selector, in case you have more than one. (Multiple different <Chapter /> components sharing the same selector would wreck the memoization). I've also split chapterCommentsSelector into a separate reselect selector so that it will be memoized, because it transforms (filters, in this case) the data from the state.
In your <Chapter /> component, you can call createChapterDataSelector(), which will give you a selector that provides an Immutable Map containing all of the data you'll need for that <Chapter /> and all of its descendants. Then you can simply pass the props down normally.
Two major benefits of passing props the normal React way are traceable data flow and component reusability. A <Comment /> component that gets passed 'content', 'authorName', and 'authorAvatar' props to render is easy to understand and use. You can use that anywhere in your app that you want to display a comment. Imagine that your app shows a preview of a comment as it's being written. With a "dumb" component, this is trivial. But if your component requires a matching entity in your Redux store, that's a problem because that comment may not exist in the store yet if it's still being written.
However, there may come a time when it makes more sense to connect() components farther down the line. One strong case for this would be if you find that you're passing a ton of props through middle-man components that don't need them, just to get them to their final destination.
From the Redux docs:
Try to keep your presentation components separate. Create container
components by connecting them when it’s convenient. Whenever you feel
like you’re duplicating code in parent components to provide data for
same kinds of children, time to extract a container. Generally as soon
as you feel a parent knows too much about “personal” data or actions
of its children, time to extract a container. In general, try to find
a balance between understandable data flow and areas of responsibility
with your components.
The recommended approach seems to be to start with fewer connected container components, and then only extract more containers when you need to.
Redux suggests that you only connect your upper-level containers to the store. You can pass every props you want for leaves from containers. In this way, it is more easier to trace the data flow.
This is just a personal preference thing, there is nothing wrong to connect leaf-like component to the store, it just adds some complexity to your data flow, thus increase the difficulty to debug.
If you find out that in your app, it is much easier to connect a leaf-like component to the store, then I suggest do it. But it shouldn't happen very often.

Categories

Resources