ReactJS issue on my test app - reactjs

So, I've been working through my first ReactJS app. Just a simple form where you type in a movie name and it fetches the data from IMDB and adds them as a module on the page. That's all working fine.
However each movie module also had a remove button which should remove that particular module and trigger a re-render. That's not working great as no matter which button you click it always removes the last movie module added rather than the one you're clicking on.
App:
http://lukeharrison.net/react/
Github codebase:
https://github.com/WebDevLuke/React-Movies
I'm just wondering if anybody can spot the reasoning behind this?
Cheers!

Just a hunch, but you should use a unique key, not just the index of the map function. This way React will understand that the movies are identified not by some iterating index, but an actual value, and that will probably solve your issue.
var movies = this.state.movies.map(function(movie, index){
return (
<Movie key={movie} useKey={index} removeMovieFunction={component.removeMovie} search={movie} toggleError={component.toggleError} />
);
});
This is because React re-evaluates your properties, sees that nothing has changed, and just removes the last <Movie /> from the list. Each Movie's componentDidMount function never runs more than once, and the state of Movie 1, Movie 2 and Movie 3 persists. So even if you supply search={movie} it doesn't do anything, because this.props.search is only used in componentDidMount.

I'm not exactly sure why it isn't rendering correctly as the dataset looks fine.
Looking at the code, I would change your remove function to this...
var index = this.state.movies.indexOf(movieToRemove);
console.log(this.state.movies);
if (index > -1) {
this.state.movies.splice(index, 1);
}
console.log(this.state.movies);
this.setState(this.state.movies);
My assumption is that, the state isn't being updated correctly. Whenever updating state, you should always use setState (unless the convention changed and I wasn't aware).
Also, you shouldn't need to explicitly call forceUpdate. Once setState is called, React will automatically do what it needs to and rerender with the new state.

State should be unidirectional (passed top down) from your top level component (known as a container). In this instance, you have state in your top level component for search strings and then you load individual movie data from within the "Movie" component itself via the IMDB API.
You should refactor your code to handle all state at the top level container and only pass the complete movie data to the dumb "Movie" component. all it should care about is rendering what you pass in it's props and not about getting it's own data.

Related

React don't mount component until needed but don't unmount afterwards in list of components

Say I am building an instant messaging with app with React (I'm not doing that exactly, but this is easier to explain). I have a sidebar with a list of conversations and, when you click one, it is shown on the right (similar to this). I don't want to mount each conversation component until the user clicks it, but I don't want to unmount it, just hide it, when they click on another conversation. How can I do this cleanly? There will never be more than about 30 chats for any user.
You can store the enabled conversations in an array that you use to show, and when you disable a conversation you can just add a hidden prop to it which you pass to the conversation and make it return null. This will make it not render anything but will not unmount it since you have not removed it from the array that handles the display of conversations.
example at: https://codesandbox.io/s/wispy-forest-59bqj
This is a bit hard to answer since you haven't posted the code.
But, theoretically, the best way to approach this problem is to transfer the data from your sidebar component and load it onto the right component on a per-user basis. You don't have to mount each "conversation component".
You can do this by with the boolean hidden property in your markup. React will render as usual and simply pass it along to the html, the browser will then simply not paint it.
const HideMe = ({ isHidden }) => (
<div hidden={isHidden}>
can you see me?
</div>
)
I made an example for you:
https://codesandbox.io/s/elastic-curie-t4ill?file=/src/App.js
reference: https://www.w3schools.com/tags/att_hidden.asp

React -> State Management

I am a beginner with React. I want to build an application similar to the page (howmuchtomakeanapp.com). That means that for every choice on a route a user does, a price will be added to the total, but when going back with the back button the price will return to the previous one. I know it is state management but I actually have no clue how to do it even after reading the whole react docs.
The React state is stored locally within a component. When it needs to be shared with other components, it is passed through props. In practice, this means that the component that needs access to a changeable value will keep that value in its state and if it can be changed by subcomponents a callback must be passed to handle the change.
To learn more about components and props, this is the link to the React documentation itself: https://reactjs.org/docs/components-and-props.html.
I recommend you take a look at Redux (https://redux.js.org/), that is a predictable state container for JavaScript apps.
Based on your description I assume you keep your price as a number and add to it the more steps you go further?
I dont think you need to do anything special besides:
My approach would be to keep an array with all the prices, that way you can always remove the last when you click on the prev button. Add one price everytime you select one and navigate to the next route. To display the total you just add the array together:
const prices = [3.5,5,10,5.76,23];
const total = prices.reduce((p, c) => p + c, 0);
Does that help?

How to update match.params?

The react app has search page. There are input.
The path is 'search/:query', and by default you see zero results.
If you go to 'search/star%20wars' you will see some results. In componentDidMount() I added if statement to load result if match.params.query is not null.
If I type into search input Spider Man and click submit - I trigger a search and show results. But if you reload page - you will see the result about Star Wars. So how update match.params.query? Or may be there other solution of fix this.
You need to update the history object as well.
What you are doing is altering the history object available to you and calculating the results based on that object. But when you will refresh the page it still holds the original history object.
One way of doing it, you need to push or replace a new route in the history.
Because evert search page is a new page, so if you want the previous pages to stay preserved you should use history.push otherwise history.replace
Implement it like this:
var routeObj = {
pathname: samePath,
state: sameState,
query: newQuery
}
//push it in your history using which ever routing library you are using.
//For Example:
router.history.replace(routeObj);
Note: Do not worry about rendering speed on changing the history. React is smart enough to handle that. Basically whenever you will push a route whose component is already mounted it will not unmount and remount the same component again, rather it will just change the props and will re render it.
The callback for this case will be => componentWillReceiveProps
#misha-from-lviv The way I see your problem statement is that you have two source of truth on is the query params, using which you should update your state, and the other is the default state which is populated from the default value of your filters.
As #Akash Bhandwalkar suggested, you do need to update the route in using the History API. But also you also a need a top-level orchestrator for your application state, which will allow you to read and write to the history api ( change your route ) and also do an XHR / fetch for you to get the results.
How I'd approach this is that I'd start with a Parent component, namely FiltersContainer , which actually does this orchestration to read and write to the url. This Container would have all the side-effect knowledge for fetching and updating the routes ( error handling included ). Now the all the child components ( filters and search results maybe ) will just read the state thus orchestrated and re-render.
Hope this guides your thinking. Do revert here if you need further guidance. 😇
Cheers! 🍻

Does React have keep-alive like Vue js?

I made a Todo list with React js. This web has List and Detail pages.
There is a list and 1 list has 10 items. When user scroll bottom, next page data will be loaded.
user click 40th item -> watch detail page (react-router) -> click back button
The main page scroll top of the page and get 1st page data again.
How to restore scroll position and datas without Ajax call?
When I used Vue js, i’ve used 'keep-alive' element.
Help me. Thank you :)
If you are working with react-router
Component can not be cached while going forward or back which lead to losing data and interaction while using Route
Component would be unmounted when Route was unmatched
After reading source code of Route we found that using children prop as a function could help to control rendering behavior.
Hiding instead of Removing would fix this issue.
I am already fixed it with my tools react-router-cache-route
Usage
Replace <Route> with <CacheRoute>
Replace <Switch> with <CacheSwitch>
If you want real <KeepAlive /> for React
I have my implementation react-activation
Online Demo
Usage
import KeepAlive, { AliveScope } from 'react-activation'
function App() {
const [show, setShow] = useState(true)
return (
<AliveScope>
<button onClick={() => setShow(show => !show)}>Toggle</button>
{show && (
<KeepAlive>
<Test />
</KeepAlive>
)}
</AliveScope>
)
}
The implementation principle is easy to say.
Because React will unload components that are in the intrinsic component hierarchy, we need to extract the components in <KeepAlive>, that is, their children props, and render them into a component that will not be unloaded.
Until now the awnser is no unfortunately. But there's a issue about it in React repository: https://github.com/facebook/react/issues/12039
keep-alive is really nice. Generally, if you want to preserve state, you look at using a Flux (Redux lib) design pattern to store your data in a global store. You can even add this to a single component use case and not use it anywhere else if you wish.
If you need to keep the component around you can look at hoisting the component up and adding a "display: none" style to the component there. This will preserve the Node and thus the component state along with it.
Worth noting also is the "key" field helps the React engine figure out what tree should be unmounted and what should be kept. If you have the same component and want to preserve its state across multiple usages, maintain the key value. Conversely, if you want to ensure an unmount, just change the key value.
While searching for the same, I found this library, which is said to be doing the same. Have not used though - https://www.npmjs.com/package/react-keep-alive

Weird interaction 'react-virtualize'd working with 'react-sortable-hoc'

https://codesandbox.io/s/qYEvQEl0
So I wrote a demo myself that didn't work compare to one sample that works, the only difference being one props for <List/>
rowHeight={({ index }) => 50}
rowHeight={50}
I use the latter way ofc. And it is not working. Why?
Answer copied from the duplicate GitHub issue you filed ;)
This creates a new function prop each time you render:
rowHeight={({ index }) => 50}
The prop-change is sufficient to trigger a re-render of the child component even if no other properties changed. In the second example above, no props change at all and so List doesn't know it needs to re-render. (Check out the section on "pure components" in the docs for more info.)
In this case, you could pass a small attribute that changes each time sort order changes (eg an incremented counter) to let the component know to re-render. You could also call forceUpdate.

Resources