I have 2 useEffect hooks for fetching all cars, then user can select some options, and component will rerendered with new fetched data, considering selected options, further user can navigate to some component and after that go back and all data will be fetched with initial state (all selected options will be erased). How to save selected options and use it for next renderings ? Furthermore It would be better to use only one hook, but i was confused in logic.
useEffect(() => {
fetchManufacturers().then(data => car.setManufacturers(data))
fetchCarNames().then(data => car.setCarNames(data))
fetchCars( null, null, 1, car.limit).then(data => {
car.setCars(data.rows)
car.setTotalCount(data.count)
})
},[car, car.page, car.selectedManufacturer, car.selectedCarName])
useEffect(() => {
fetchCars(car.selectedManufacturer.id , car.selectedCarName.id, car.page, car.limit).then(data => {
car.setCars(data.rows)
car.setTotalCount(data.count)
})
}, [car.page, car.selectedManufacturer, car.selectedCarName])
I tried to use getters and setters, but it save the 1 step back selected option.
You could either store your data in a parent component, but I suggest storing it in a context. Since it is related to a user decision, it could be interesting to get these data from wherever you want.
You can find a simple example on how to use a the React contextApi in my answer to another post.
There is other state management, but I like this one for its simplicity.
React Redux is great for what you're trying to do. You can fetch all the raw car data once and save it in Redux. Every component can then fetch that data as needed. You can even store your user's car selections in Redux if you need to share that state between components.
Related
I'm new in react and i'm studing a method to make connections with Firebase to my Project.
With props I can pass an ID and search it in firebase, but when i reload my page, all the props are lost and i have this message in the image:
The value is Undefined because the props are losing their values
Is there any other way for this information don't lost?
I dont try anything because idk what to do.
The ID in the props will be used in useEffect() on my page.
Edit:
There is the Cards Image:
Cards
And here it's when i click on the card "Pinscher":
The page when i click on Pinscher
But when i Reload the page "Pinscher", i lost the Id passed with props.
The most straightforward way I know to persist data over a reload is to save it to localstorage or sessionstorage. Which one to use--and whether it's appropriate--depend on your actual use case, which we can't comment on without more knowledge of your project.
Assuming you did want to use that, a basic implementation would be to write helper functions when you set state to also save the data to storage. Your initial state load would then look for storage information as its default and then fallback to null if it can't find anything in storage.
const [arbData, setArbData] = useState(window.localStorage.getItem('arbData'));
const setArbDataWrapper = (data) => {
setArbData(data);
window.localStorage.set('arbData', data);
}
I've just started using Recoil on a new project and I'm not sure if there is a better way to accomplish this.
My app is an interface to basically edit a JSON file containing an array of objects. It reads the file in, groups the objects based on a specific property into tabs, and then a user can navigate the tabs, see the few hundred values per tab, make changes and then save the changes.
I'm using recoil because it allows me to access the state of each input from anywhere in my app, which makes saving much easier - in theory...
In order to generate State for each object in the JSON file, I've created an component that returns null and I map over the initial array, create the component, which creates Recoil state using an AtomFamily, and then also saves the ID to another piece of Recoil state so I can keep a list of everything.
Question 1 Is these a better way to do this? The null component doesn't feel right, but storing the whole array in a single piece of state causes a re-render of everything on every keypress.
To Save the data, I have a button which calls a function. That function just needs to get the ID's, loop through them, get the state of each one, and push them into an Array. I've done this with a Selector too, but the issue is that I can't call getRecoilValue from a function because of the Rules of Hooks - but if I make the value available to the parent component, it again slows everything right down.
Question 2 I'm pretty sure I'm missing the right way to think about storing state and using hooks, but I haven't found any samples for this particular use case - needing to generate the state up front, and then accessing it all again on Save. Any guidance?
Question 1
Get accustomed to null-rendering components, you almost can't avoid them with Recoil and, more in general, this hooks-first React world π
About the useRecoilValue inside a function: you're right, you should leverage useRecoilCallback for that kind of task. With useRecoilCallback you have a central point where you can get and set whatever you want at once. Take a look at this working CodeSandbox where I tried to replicate (the most minimal way) your use-case. The SaveData component (a dedicated component is not necessary, you could just expose the Recoil callback without creating an ad-hoc component) is the following
const SaveData = () => {
const saveData = useRecoilCallback(({ snapshot }) => async () => {
const ids = await snapshot.getPromise(carIds);
for (const carId of ids) {
const car = await snapshot.getPromise(cars(carId));
const carIndex = db.findIndex(({ id }) => id === carId);
db[carIndex] = car;
}
console.log("Data saved, new `db` is");
console.log(JSON.stringify(db, null, 2));
});
return <button onClick={saveData}>Save data</button>;
};
as you can see:
it retrieves all the ids through const ids = await snapshot.getPromise(carIds);
it uses the ids to retrieve all the cars from the atom family const car = await snapshot.getPromise(cars(carId));
All of that in a central point, without hooks and without subscribing the component to atoms updates.
Question 2
There are a few approaches for your use case:
creating empty atoms when the app starts, updating them, and saving them in the end. It's what my CodeSandbox does
doing the same but initializing the atoms through RecoilRoot' initialState prop
being updated by Recoil about every atom change. This is possible with useRecoilTransactionObserver but please, note that it's currently marked as unstable. A new way to do the same will be available soon (I guess) but at the moment it's the only solution
The latter is the "smarter" approach but it really depends on your use case, it's up to you to think if you really want to update the JSON at every atom' update π
I hope it helps, let me know if I missed something π
I've structured my redux application such that my data models are handled on separate branches of the state tree.
{concerts, venues}
I've also used react-navigation-redux-helpers to put my navigation state into the tree:
{concerts, venues, nav}
However, I want to record information about the visibility state of a particular model. When the ConcertScreen is shown, I want to know when a user's looking/stops looking at a particular concert ID (and letting the server know), with the eventual goal of measuring how long a particular concert ID was visible on screen.
I've done this by adding branches for Navigation/NAVIGATE, Navigation/RESET, and Navigation/BACK to the concerts reducer, and setting visible: true on the appropriate object under concerts.
This has been error prone, since the navigation state can be modified by actions other than these specific actions. (A logout action handled directly by the nav reducer, for example.)
I see two alternatives, both not ideal:
Use props.navigation.addListener to listen to focus and blur events on the ConcertScreen, trigger custom concertFocused/concertBlurred actions, and handle those in my concert reducer instead of the Navigation/* actions.
Create a selector that computes the currently visible concert from the nav state and refactor the business logic that expects concert.visible as input to use the selector instead.
The problem with 1 seems to be that it's adding overhead to the event loop, all the extra actions flying around means extra rendering overhead.
2 avoids the extra actions, but it seems like a lot of refactoring for not a whole lot of gain, and it means I have to move business logic out of the concert reducer and put it elsewhere.
Say I use option 2. I add a middleware that, on any action, applies the selector to state.nav and from that computes what Concert is currently displayed. If I wanted to measure duration, how would I store start/end time? Fire a new action with that added data so the concert reducer catches it? That just seems like option 1 with extra steps.
I could also have this middleware add a field to every action indicating the concert display state, and have the concert reducer handle it in the default/fallthrough case. Do people do that?
I would approach your use-case in such a way, that I will get the best of both solutions.
First of all, having many actions dispatched you're worried about rendering overhead. Using a selector library, let's say reselect, the library memoization will prevent unnecessarily components rerendering.
Later on, if I understand you correctly, your goal is to let the server know the visibility status of an item (concert) and eventually its visible time. If your goal is notifying the server only, without letting know the rest app's front-end users, why do you want to keep tracking it in Redux too? You can skip the Redux part and send updates to the server only.
Let's assume, that you need Redux for the tracking. You can try on your way structuring the Store, as you already mentioned, adding the visible flag to each object in the Redux store. But if your items' structure is bigger enough and it's costly to copy and update the object each time when changing the visible flag, you can consider creating a dedicated Store branch and reducer, that will be responsible only for the tracking needs. Something like that:
tracking : {
concerts: {
1: { visible: true, time: 10 }
}
}
Now, updating an item's flag, only the above tiny structure has to be copied and modified. Even, you can make it smaller and more specific for a certain item type (trackingConcerts).
* Keep in mind, it's on your own to decide whether or not such a dedicated Store branch will improve the performance, because we don't know your detailed architecture and Store specifics.
Continuing with the solutions ...
Relying on navigation actions + middleware is error prone, as you mentioned. What about the use-case you have a general page of components (i.e. navigation action with generic name will be dispatched), but you render there one of your items (concert)? Also rendering an item, would be always coupled with modifying the mapping logic in your middleware or wherever place you track the items by action name. Another tricky case is when you render different type of items (concerts, venues) on exactly one page. How will you differ and track the items, considering you have only 1 navigation item? Also in a such setup, I don't see a straightforward way for handling an item visible time.
About the selectors as a solution - they can be only a small part of the solution. The selector is responsible for selecting and managing derived state. Nothing more.
Show me the code, please.
I would create a wrapper component around react on screen (or any similar library that tracks component visibility) and implement only the tracking visible time of the component.
The wrapper will trigger callbacks when the component visibility state is changed and a callback on componentDidUnmount including the visible time.
That's all! Now you can attach handlers on these callbacks and you can update your Redux and/or notify the server for the visibility changes, without relying on any navigation actions and middlewares.
Usage:
const App = () => (
<Tracking
onVisibilityChange={isVisible => {}}
onUnmount={visibleSeconds => {}}
>
<Concert id={1} />
</Tracking>
)
Tracking Wrapper:
import TrackVisibility from 'react-on-screen'
const Tracking = ({ children, libraryProps, ...rest }) => (
<TrackVisibility {...libraryProps}>
<TrackingCore {...rest}>
{children}
</TrackingCore>
</TrackVisibility>
)
TrackingCore, our custom tracking logic:
class TrackingCore extends React.Component {
constructor (props) {
super(props)
this.state = {
visibleSeconds: 0,
interval: null
}
}
componentDidMount() {
this.track()
}
componentWillReceiveProps (nextProps) {
this.track(nextProps)
}
componentDidUnmount() {
const { visibleSeconds, interval } = this.state
const { onUnmount } = this.props
onUnmount(visibleSeconds)
clearInterval(interval)
}
track (nextProps) {
const { isVisible, onVisibilityChange } = this.props
const { visibleSeconds, interval } = this.state
const hasVisibilityChanged = (isVisible !== nextProps.isVisible) || !nextProps
const isVisibleValue = nextProps ? nextProps.isVisible : isVisible
// On visibility change, invoke the callback prop
if (hasVisibilityChanged) {
onVisibilityChange(isVisibleValue)
// If it becomes visible, start counting the `visibleSeconds`
if (isVisibleValue) {
this.setState({
interval: setInterval(() => this.setState({
visibleSeconds: visibleSeconds + 1
}), 1000)
})
} else {
clearInterval(interval)
}
}
}
render () {
return this.props.children
}
}
For example I have two components - ListOfGroupsPage and GroupPage.
In ListOfGroupsPage I load list of groups from the server and store it to the state.groups
In route I have mapping like βgroup/:idβ for GroupPage
When this address is loaded, the app shows GroupPage, and here I get the data for group from state.groups (try to find group in state via id).
All works fine.
But if I reload page, I'm still on page /group/2, so GroupPage is shown. But state is empty, so the app can't find the group.
What is the proper way to load data in React + Redux? I can see this ways:
1) Load all data in root component. It will be very big overhead from traffic side
2) Don't rely on store, try to load required data on each component. It's more safe way. But I don't think that load the same data for each component - it's cool idea. Then we don't need the state - because each component will fetch the data from server
3) ??? Probably add some kind of checking in each component - first try to find required data in store. If can't - load from the server. But it requires much of logic in each component.
So, is there the best solution to fetch data from server in case of usage Redux + ReactJS?
One approach to this is to use redux-thunk to check if the data exist in the redux store and if not, send a server request to load the missing info.
Your GroupPage component will look something like
class GroupPage extends Component {
componentWillMount() {
const groupId = this.props.params.groupId
this.props.loadGroupPage(groupId);
}
...
}
And in your action...
const loadGroupPage = (groupId) => (dispatch, getState) => {
// check if data is in redux store
// assuming your state.groups is object with ids as property
const {
groups: {
[groupId]: groupPageData = false
}
} = getState();
if (!groupPageData) {
//fetch data from the server
dispatch(...)
}
}
I recommend caching the information on the client using localstorage. Persist your Redux state, or important parts of it, to localstorage on state change, and check for existing records in localstorage on load. Since the data would be on the client, it would be simple and quick to retrieve.
The way I approach this is to fetch from the server straight after the store has been created. I do this by dispatching actions. I also use thunks to set isFetching = true upon a *_REQUEST and set that back to false after a *_SUCCESS or *_FAILURE. This allows me to display the user things like a progress bar or spinner. I think you're probably overestimating the 'traffic' issue because it will be executed asynchronosly as long as you structure your components in a way that won't break if that particular part of the store is empty.
The issue you're seeing of "can't get groups of undefined" (you mentioned in a comment) is probably because you've got an object and are doing .groups on it. That object is most likely empty because it hasn't been populated. There are couple of things to consider here:
Using ternary operators in your components to check that someObject.groups isn't null; or
Detailing in the initialState for someObject.groups to be an empty array. That way if you were to do .map it would not error.
Use selectors to retrieve the list of groups and if someObject.groups is null return an empty array.
You can see an example of how I did this in a small test app. Have a look at specifically:
/src/index.js for the initial dispatch
/src/redux/modules/characters.js for the use of thunks
/src/redux/selectors/characters.js for the population of the comics, series, etc. which are used in the CharacterDetails component
I'm trying to implement, let's say Twitter. I'm doing something like
initialState = {
tweets: {id => tweet}
}
then, a user goes to his timeline, now the action fetchTweets fetches all his tweets. However, then he can post a tweet, tweet T. But if I don't manually insert the posted T into state.tweets, he will not see this tweet in his timeline.
So here comes the question, when a user did some actions on his page, is that a good point to refresh the data? How does redux avoid data stale in this kind of case?
Thanks!
It's a bit of a paradigm shift when using Redux, but you don't want any functions to be your state. So, that initial state should actually just be either null or an empty object (depending on the design of the components that receive those props). But to answer your question a bit more directly, if you want to make sure that data stays "fresh" you need to make it happen. There is no magic in Redux, which is a GOOD THING.
However, if you design your code properly, the user shouldn't experience something resembling a full page refresh. At a high level, here is how I might design what you are describing.
Write actions for requesting and receiving tweets, i.e.:
export function requestTweets() => {
return {
type: REQUEST_TWEETS
}
}
export function receiveTweets(tweets) => {
return {
type: RECEIVE_TWEETS,
payload: tweets
}
}
Then wrap that in a "public" function that can be reused wherever:
export const fetchTweets = () => {
return (dispatch, getState) => {
dispatch(requestTweets())
//network code here is pseudocode
fetch('https://tweeter/api/v1/tweets')
.then(data => dispatch(receiveTweets(data))
.catch(err => displayErrorMsg(err))
}
}
Then in your action handlers just reduce the tweets into the next state.
Now with fetchTweets, you can call that on the first load, after posting or on an interval. The nice thing is React will handle the diffing well for you and not re-render the entire page. It's just up to you to design it well so the user notices when and where new tweets come in.
Hope that helps!
Redux manages your state for you. It tells anyone who wants to hear about any changes in the state, such as React-Redux.
It does not do anything to help you with getting data, such as tweets. Nor does it help you decide when to get this data.
You'll have to decide for yourself when to do this and you can use setTimeout or anything else you feel like.
It's probably best if you manually insert the tweet into the state rather than refetch when you post. It's so much more responsive.