I'm currently developing an App in which multiple screens use the same data. Rather than declaring multiple loose functions and passing the same props everytime, I've nested them all in the same function that returns the navigator, which contains all data I need to use between the screens.
...imports...
const Drawer = createDrawerNavigator();
const HomeNav = ({route, navigation }) =\> {
... states, variables, and functions
const Scr1 = ({ navigation }) => {
... stuff with states and data above
);
}
const Scr2 = ({ navigation }) => {
... other stuff
);
}
return (
<Drawer.Navigator screenOptions={{drawerStyle:{backgroundColor:'#000000'}, drawerActiveTintColor:'#ffffff', drawerInactiveTintColor:'#DADADA'}}
initialRouteName="Casa"
drawerContent={(props) => <CustomDrawerContent {...props} />}>
... drawers
</Drawer.Navigator>
)
that's a cut down version of my code, to explain better. My screens, Scr1 and Scr2, are in the same function as the navigator.
Things work most of the time. I can easily share data between screens, but I'm having a LOT of problems with undefined objects and useless rerenders. I created a hook with the data that's getting loaded as the state, so whenever it efectively loads, the screen gets rerendered. But I have multiple data variables, so the hook gets called as each one finishes loading. And if I remove those hooks, I lose my defined objects.
Anyway, bottom line, my question is if what I'm doing could give me problems in the future, if it's the 'right' way to do things as to not complicate further down the road. If so, I find a way to deal with the rerenders, otherwise I change my code while I'm still beggining.
Full Homenav.js on github
That's the complete file, if it helps
pretty messy, but I'm learning react so I'm just going at it
Thanks in advance!
Related
I have a .tsx file that renders two component:
export default observer(function MyModule(props: MyModuleProps) {
....
return (
<div>
<TopPart></TopPart>
<LowerPart></LowerPart>
</div>
);
});
The problem I have is that the TopPart contains lots of sub components, so it takes much longer time to render, and the LowerPart is more important and I want to render it first, but in this code, the LowerPart won't be available until the TopPart has been rendered.
What I want to do is to first render the LowerPart, then the TopPart, without changing the layout. I am wondering how I can achieve this goal properly.
Disclaimer: this is a hack.
If the problem is server side, this is easy for react. Just throw up a placeholder while data is loading, then save it in state when loading finishes and render.
The following answer assumes this is a rendering performance problem. If it is, then you look at that rendering performance. Paginate your lists, simplify your CSS rules, profile react and see what is taking the time.
What follows may be interesting, but is probably a bad idea. React is declarative, meaning you tell the result you want and then let it crunch things to deliver that. As soon as you start telling it what order to do things in, you break that paradigm and things may get painful for you.
If you want to break up rendering you could use state to prevent the expensive component from rendering, and then use an effect to update that state after the first render, which then renders both components.
You could make a custom hook like this:
function useDeferredRender(): boolean {
const [doRender, setDoRender] = useState(false);
useEffect(() => {
if (!doRender) {
setTimeout(() => setDoRender(true), 100);
}
}, [doRender]);
return doRender;
}
This hook create the doRender state, initialized to false. Then it has an effect which sets the state to true after a brief timeout. This means that doRender will be false on the first render, and then the hook will immediately set doRender to true, which triggers a new render.
The timeout period is tricky. Too small and React may decide to batch the render, too much and you waste time. (Did I mention this was a hack?)
You would this like so:
function App() {
const renderTop = useDeferredRender();
return (
<div className="App">
{renderTop ? <TopPart /> : "..."}
<LowerPart />
</div>
);
}
Working example
One last time: this is probably a bad idea.
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
I am trying to learn React by building a web application. Since I want to learn it step by step, for now I don't use Redux, I use only the React state and I have an issue.
This is my components architecture:
App.js
|
_________|_________
| |
Main.js Side.js
| |
Game.js Moves.js
As you can see, I have the main file called App.js, in the left side we have the Main.js which is the central part of the application which contains Game.js where actually my game is happening. On the right side we have Side.js which is the sidebar where I want to display the moves each player does in the game. They will be displayed in Moves.js.
To be more clear think at the chess game. In the left part you actually play the game and in the right part your moves will be listed.
Now I will show you my code and explain what the problem is.
// App.js
const App = React.memo(props => {
let [moveList, setMovesList] = useState([]);
return (
<React.Fragment>
<div className="col-8">
<Main setMovesList={setMovesList} />
</div>
<div className="col-4">
<Side moveList={moveList} />
</div>
</React.Fragment>
);
});
// Main.js
const Main = React.memo(props => {
return (
<React.Fragment>
<Game setMovesList={props.setMovesList} />
</React.Fragment>
);
});
// Game.js
const Game= React.memo(props => {
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
return () => {
document.getElementById('board').removeEventListener('click', executeMove, false);
};
})
return (
// render the game board
);
});
// Side.js
const Side= React.memo(props => {
return (
<React.Fragment>
<Moves moveList={props.moveList} />
</React.Fragment>
);
});
// Moves.js
const Moves= React.memo(props => {
let [listItems, setListItems] = useState([]);
useEffect(() => {
let items = [];
for (let i = 0; i < props.moveList.length; i++) {
items.push(<div key={i+1}><div>{i+1}</div><div>{props.moveList[i]}</div></div>)
}
setListItems(items);
return () => {
console.log('why this is being triggered on each move?')
};
}, [props.moveList]);
return (
<React.Fragment>
{listItems}
</React.Fragment>
);
});
As you can see on my code, I have defined the state in App.js. On the left side I pass the function which updates the state based on the moves the player does. On the right side I pass the state in order to update the view.
My problem is that on each click event inside Game.js the component Moves.js unmounts and that console.log is being triggered and I wasn't expected it to behave like that. I was expecting that it will unmount only when I change a view to another.
Any idea why this is happening ? Feel free to ask me anything if what I wrote does not make sense.
Thanks for explaining your question so well - it was really easy to understand.
Now, the thing is, your component isn't actually unmounting. You've passed props.movesList as a dependency for the usEffect. Now the first time your useEffect is triggered, it will set up the return statement. The next time the useEffect gets triggered due to a change in props.movesList, the return statement will get executed.
If you intend to execute something on unmount of a component - shift it to another useEffect with an empty dependency array.
answering your question
The answer to your question
"why this is being triggered on each move"
would be:
"because useEffect wants to update the component with the changed state"
But I would be inclined to say:
"you should not ask this question, you should not care"
understanding useEffect
You should understand useEffect as something that makes sure the state is up to date, not as a kind of lifecycle hook.
Imagine for a moment that useEffect gets called all the time, over and over again, just to make sure everything is up to date. This is not true, but this mental model might help to understand.
You don't care if and when useEffect gets called, you only care about if the state is correct.
The function returned from useEffect should clean up its own stuff (e.g. the eventlisteners), again, making sure everything is clean and up to date, but it is not a onUnmount handler.
understanding React hooks
You should get used to the idea that every functional component and every hook is called over and over again. React decides if it might not be necessary.
If you really have performance problems, you might use e.g. React.memo and useCallback, but even then, do not rely on that anything is not called anymore.
React might call your function anyway, if it thinks it is necessary. Use React.memo only as kind of a hint to react to do some optimization here.
more React tips
work on state
display the state
E.g. do not create a list of <div>, as you did, instead, create a list of e.g. objects, and render that list inside the view. You might even create an own MovesView component, only displaying the list. That might be a bit too much separation in your example, but you should get used to the idea, also I assume your real component will be much bigger at the end.
Don’t be afraid to split components into smaller components.
It seems the problem is occurred by Game element.
It triggers addEventListener on every render.
Why not use onClick event handler
/* remove this part
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
})
*/
const executeMove = (e) => {
props.setMovesList(e.target);
}
return (
<div id="board" onClick={executeMove}>
...
</div>
)
If you want to use addEventListener, it should be added when the component mounted. Pass empty array([]) to useEffect as second parameter.
useEffect(() => {
function executeMove(e) {
props.setMovesList(e.target);
}
document.getElementById('board').addEventListener('click', executeMove, false);
}, [])
I'm trying to build a React Native app, but am still kind of new to the React/RN ecosystem, so I may just be misunderstanding something obvious with the problem I'm having.
I have an app where a lot of the pages are structured as follows:
<View>
<NavComponent />
<View>
{/* Page component structure/logic here */}
</View>
</View>
NavComponent loads a toggleable nav menu with TouchableOpacity elements like the following:
Go to Screen #1
Go to Screen #2
Go to Screen #3
The problem I'm having (and maybe this isn't a problem so much as just how React/RN works) is that if I start on screen #1, open the nav, go to screen #2, open the nav again, and then go back to screen #1, even though screen #1 shows up again, the actual rendering function for screen #1 doesn't seem to be called again, and since NavComponent is part of the rendering of each screen, when I try to open the nav from screen #1 again, I get the following warning:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a
useEffect cleanup function ...
Again, maybe my approach with the app is flawed to begin with, but essentially, when I go from one screen to another from the nav, I always want the new screen to re-render from scratch (including the initial Ajax call for data).
Here's a more fleshed-out example of the render function for a screen (they all follow this same basic pattern):
const screen1 = ({ navigation }) => {
const [serverData, setServerData] = useState(null);
useEffect(() => {
// getPageData is a custom method I added to axios.
axios.getPageData('/api/url/here', (data) => {
setServerData(data);
});
}, []);
if (serverData) {
const { meta, user, data } = serverData;
return (
<View>
<NavComponent />
<View style={styles.container}>
{/* Page component structure/logic here */}
</View>
</View>
);
}
return null;
};
If, for example, I added a console.log to the beginning of the render function above, it's called the first time the screen is loaded, but if I go to screen #2 and then come back to screen #1 via the nav component, the console.log isn't output again. Why?
And for what it's worth, I'm using the standard navigation.navigate('ScreenName') in NavComponent to go from screen to screen.
Any advice on how to fix the warning (and/or just better design the app) so that I can have that nav on every page would be greatly appreciated. Thank you.
your api call is resulting in the warning, in the react/native ecosystem, when a component is removed from the tree, the developer needs to cancel all subscriptions (listeners to events) and async tasks(fetching data from the web), those function need to be canceld by the developer as react/native cant do that for you.
to handle that in a class based component, you need to impelment componentWillUnmount and remove the subscriptions there
class MyClass extends Component {
componentWillUnmount() {
// remove listeners and cancel requests
}
but in a modern hook component , you need to return a cleanup function, a function to return in useEffect that will be called by react to cancel any subscriptions you have made, in your case, just return a cleanup function should remove that warning for you
const [mounted, setIsMounted] useState(false)
useEffect(() => {
// getPageData is a custom method I added to axios.
setIsMounted(true)
axios.getPageData('/api/url/here', (data) => {
if(isMounted)
setServerData(data);
});
return () => {
setIsMounted(false)
}
}, []);
I’ve been told that it’s a best practice to use classes only for containers and to use functions for components. So containers have state and components are dumb functions that only recieve and send props to and from the containers.
The problem I’m finding with this is that it leads to really bloated containers. Not only that but if a container includes many different components then the methods in these containers are a mixture of many different unrelated functionalities.
This goes against the idea of keeping everything very modular. For example if I have a “submit comment” component in my container I would expect the submitCommentHandler method to also be in the relevant component not mixed together in the Post container with a ton of other handlers for unrelated functionalities like being next to the ratePostHandler and the userLoginHandler.
I’m new to react so maybe I’m missing something but how to reconcile this “best practice” with all the other issues it presents?
There are a couple of misconceptions in your post, possibly stemming from misconceptions in whatever best practices article you are reading.
When the core idea of containers + components started surfacing many examples were not doing it correctly.
// DO NOT DO THIS
let FormComponent = ({ data, handleSubmit }) =>
<form onSubmit={handleSubmit}>
{...something with data...}
</form>
class FormContainer extends React.Component {
state = { data: [] }
submitForm = formData => {
api.post(formData).then(data => this.setState({ data }))
}
render() {
return (
<div>
<FormComponent
data={this.state.data}
handleSubmit={this.submitForm}
/>
</div>
)
}
}
This is a pretty classic container + component example, however it's 100% useless and goes against the entire idea. In order for containers to be modular they need to be completely agnostic about render logic. If you hardcode the presentation in the container, then it's all just one big component split into two sections, somewhat arbitrarily I might add.
You can read about higher order components, but I'm going to focus on standard that's gaining traction: render props.
class FormContainer extends React.Component {
state = { data: [] }
submitForm = formData => {
api.post(formData).then(data => this.setState({ data }))
}
render() {
return this.props.children({
data: this.state.data,
submitForm: this.submitForm
})
}
}
Now you have a container that does one thing, that can be used repeatedly anywhere in your app, with any presentational component like so:
let SomewhereInYourApp = () => (
<FormContainer>
{({ data, submitForm }) => (
<div>
{/* put whatever you want here.. it's modular! */}
<FormComponent data={data} handleSubmit={submitForm} />
</div>
)}
</FormContainer>
)
Then you can make as many containers as you need which only do the specific business logic that's important to it and nest them however it makes sense to do so. I don't think any best practices say to combine everything into a single container. Nesting containers is perfectly acceptable.
If you have many containers and the nesting gets a little too pyramidy, consider using HoCs or a utility like react-adopt to compose containers that use render props.
Screenshot of composed containers from react-adopt: