Update single element of the array in redux state - arrays

I have a redux state that contains an array of objects, for each of these object I call an api to get more data
objects.forEach((obj, index) => {
let newObj = { ...obj };
service.getMoreData()
.then(result => {
newObj.data = result;
let newObjects = [...this.props.objectsList] ;
let index = newObjects.findIndex(el => el.id === newObj.id);
if (index != -1) {
newObjects[index] = newObj;
this.props.updateMyState({ objectsList: newObjects });
}
})
When I get two very close responses the state is not updated correctly, I lose the data of the first response.
What is the right way to update a single element of the array? Thanks!

So since i don't know what service is and there isn't that much here to go off, here is what I would do from my understanding of what it looks like your doing:
So first let's set up a reducer to handle the part of redux state that you want to modify:
// going to give the reducer a default state
// array just because I don't know
// the full use case
// you have an id in your example so this is the best I can do :(
const defaultState = [{ id: 123456 }, { id: 123457 }];
const someReducer = (state=defaultState, action) => {
switch (action.type) {
// this is the main thing we're gonna use
case 'UPDATE_REDUX_ARRAY':
return [
...action.data
]
// return a default state == the state argument
default:
return [
...state
]
}
}
export default someReducer;
Next you should set up some actions for the reducer, this is optional and you can do it all inline in your component but I'd personally do it this way:
// pass data to the reducer using an action
const updateReduxArray = data => {
return {
type: 'UPDATE_REDUX_ARRAY',
data: data
}
}
// export like this because there might
// be more actions to add later
export {
updateReduxArray
}
Then use the reducer and action with React to update / render or whatever else you want
import { useState } from 'react';
import { updateReduxArray } from 'path_to_actions_file';
import { useEffect } from 'react';
import { axios } from 'axios';
import { useSelector, useDispatch } from 'react-redux';
const SomeComponent = () => {
// set up redux dispatch
const dispatch = useDispatch();
// get your redux state
const reduxArray = useSelector(state => state.reduxArray) // your gonna have to name this however your's is named
// somewhere to store your objects (state)
const [arrayOfObjects, updateArrayOfObjects] = useState([]);
// function to get data from your API
const getData = async () => {
// I'm using axios for HTTP requests as its pretty
// easy to use
// if you use map you can just return the value of all API calls at once
const updatedData = await Promise.all(reduxArray.map(async (object, index) => {
// make the api call
const response = axios.get(`https://some_api_endpoint/${object.id}`)
.then(r => r.data)
// return the original object with the addition of the new data
return {
...response,
...object
}
}))
// once all API calls are done update the state
// you could just update redux here but this is
// a clean way of doing it incase you wanna update
// the redux state more than once
// costs more memory to do this though
updateArrayOfObjects(updatedData)
}
// basicity the same as component did mount
// if you're using classes
useEffect(() => {
// get some data from the api
getData()
}, [ ])
// every time arrayOfObjects is updated
// also update redux
useEffect(() => {
// dispatch your action to the reducer
dispatch(updateReduxArray(arrayOfObjects))
}, [arrayOfObjects])
// render something to the page??
return (
<div>
{ reduxArray.length > 0
? reduxArray.map(object => <p>I am { object.id }</p>)
: <p>nothing to see here</p>
}
</div>
)
}
export default SomeComponent;
You could also do this so that you only update one object in redux at a time but even then you'd still be better off just passing the whole array to redux so I'd do the math on the component side rather than the reducer .
Note that in the component I used react state and useEffect. You might not need to do this, you could just handle it all in one place when the component mounts but we're using React so I just showcased it incase you want to use it somewhere else :)
Also lastly I'm using react-redux here so if you don't have that set up (you should do) please go away and do that first, adding your Provider to the root component. There are plenty of guides on this.

Related

React / Redux: why useEffect is called too many times?

First of all, I'm sorry if the first question is unfamiliar and difficult to understand
Purpose:
Get data from firestore and fetch again when list of store is updated.
add info.
I want to get data from a firestore and redraw the screen when the list of stores is updated.
This is a general list update.
problem:
useEffect wants to execute fetch when the value of stores is updated, but useEffect is always called and fetch request to firestore does not stop.
The code is below.
Test.jsx
...
import { useDispatch, useSelector } from "react-redux";
import { fetchStores } from "../../reducks/stores/operations";
const storesRef = db.collection('stores');
const Test = () => {
const dispatch = useDispatch();
const selector = useSelector(state=>state);
// stores set initialState
// stores: {
// name: "",
// list: [],
// }
const stores = selector.stores.list;
useEffect(()=>{
dispatch(fetchStores())
},[stores])
}
...
reducks/stoers/operations.js
export const fetchStores = () => {
return async (dispatch) => {
storesRef.where().get()
.then(snapshots => {
const storeList = []
snapshots.forEach(snapshot => {
const store = snapshot.data();
store['id'] = snapshot.id;
storeList.push(store)
})
dispatch(fetchStoresAction(storeList))
})
}
}
*fetchStoresAction saves an array in the store list.
thank you
You should have an empty dependency array in useEffect so the fetch request only executed once when the components gets mounted:
useEffect(() => {
dispatch(fetchStores())
}, [])
If you would like to fetch stores when new store is created or updated, you should do it in a function where you can access the update or create fetch request's result. If the result is successful (successful update on database), then you can fetch your stores again.

useSelector is not working, redux in react

When I use useSelector the variable is always holding its initial state. I have the feeling it is stored in some parallel galaxy and never updated. But when I retrieve the value with const store = useStore(); store.getState()... it gives the correct value (but lacks subscribtions). When I inspect the store in redux devtools I can see all the values are recorded in the store correctly. Values are just not retrieved from the store with useSelector.
What I wanted to achieve is to have some cache for user profiles, i.e. not fetch /api/profile/25 multiple times on the same page. I don't want to think of it as "caching" and make multiple requests just keeping in mind the requests are cached and are cheap but rather thinking of it as getting profiles from the store and keeping in mind profiles are fetched when needed, I mean some lazy update.
The implementation should look like a hook, i.e.
// use pattern
const client = useProfile(userId);
// I can also put console.log here to see if the component is getting updated
let outputProfileName;
if( client.state==='pending' ) {
outputProfileName = 'loading...';
} else if( client.state==='succeeded' ) {
outputProfileName = <span>{client.data.name}</span>
} // ... etc
so I placed my code in use-profile.js, having redux-toolkit slice in profile-slice.js
profile-slice.js
import {
createSlice,
//createAsyncThunk,
} from '#reduxjs/toolkit';
const entityInitialValue = {
data: undefined,
state: 'idle',
error: null
};
export const slice = createSlice({
name: 'profile',
initialState: {entities:{}},
reducers: {
updateData: (state,action) => {
// we received data, update the data and the status to 'succeeded'
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: action.payload.data,
state: 'succeeded',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchPendStart: (state,action) => {
// no data - indicates we started fetching
state.entities[action.payload.id] = {
...entityInitialValue,
//...state.entities[action.payload.id],
data: null,
state: 'pending',
error: null
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
dispatchError: (state,action) => {
state.entities[action.payload.id] = {
//...entityInitialValue,
...state.entities[action.payload.id],
data: null,
state: 'failed',
error: action.payload.error
};
return; // I tried the other approach - return {...state,entities:{...state.entities,[action.payload.id]:{...}}} - both are updating the store, didn't notice any difference
},
},
extraReducers: {
}
});
export const {updateData,dispatchPendStart,dispatchError} = slice.actions;
// export const selectProfile... not used
export default slice.reducer;
use-profile.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector, useStore } from 'react-redux';
import {
updateData as actionUpdateData,
dispatchPendStart as actionDispatchPendStart,
dispatchError as actionDispatchError,
} from './profile-slice';
//import api...
function useProfile(userId) {
const dispatch = useDispatch();
const actionFunction = async () => {
const response = await client.get(`... api endpoint`);
return response;
};
const store = useStore();
// versionControl is a dummy variable added for testing to make sure the component is updated;
// it is updated: I tried adding console.log to my component function (where I have const client = useProfile(clientId)...)
const [versionControl,setVersionControl] = useState(0);
const updateVersion = () => setVersionControl(versionControl+1);
// TODO: useSelector not working
const updateData = newVal => { dispatch(actionUpdateData({id:userId,data:newVal})); updateVersion(); };
const dispatchPendStart = newVal => { dispatch(actionDispatchPendStart({id:userId})); updateVersion(); };
const dispatchError = newVal => { dispatch(actionDispatchError({id:userId,error:newVal})); updateVersion(); };
const [
getDataFromStoreGetter,
getLoadingStateFromStoreGetter,
getLoadingErrorFromStoreGetter,
] = [
() => (store.getState().profile.entities[userId]||{}).data,
() => (store.getState().profile.entities[userId]||{}).state,
() => (store.getState().profile.entities[userId]||{}).error,
];
const [
dataFromUseSelector,
loadingStateFromUseSelector,
loadingErrorFromUseSelector,
] = [
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].data : undefined ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' ),
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingError : undefined ),
];
useEffect( async () => {
if( !(['pending','succeeded','failed'].includes(getLoadingStateFromStoreGetter())) ) {
// if(requestOverflowCounter>100) { // TODO: protect against infinite loop of calls
dispatchPendStart();
try {
const result = await actionFunction();
updateData(result);
} catch(e) {
dispatchError(e);
throw e;
}
}
})
return {
versionControl, // "versionControl" is an approach to force component to update;
// it is updating, I added console.log to the component function and it runs, but the values
// from useSelector are the same all the time, never updated; the problem is somewhere else; useSelector is just not working
// get data() { return getDataFromStoreGetter(); }, // TODO: useSelector not working; but I need subscribtions
// get loadingState() { return getLoadingStateFromStoreGetter(); },
// get loadingError() { return getLoadingErrorFromStoreGetter(); },
data: dataFromUseSelector,
loadingState: loadingStateFromUseSelector,
loadingError: loadingErrorFromUseSelector,
};
}
export default useProfile;
store.js
import { configureStore,combineReducers } from '#reduxjs/toolkit';
import profileReducer from '../features/profile/profile-slice';
// import other reducers
export default configureStore({
reducer: {
profile: profileReducer,
// ... other reducers
},
});
component.js - actually see the use pattern above, there's nothing interesting besides the lines posted.
So
When I export loading state (I mean last lines in use-profile.js; I can suppress last three lines and uncomment the other three). So, if I use getLoadingStateFromStoreGetter (values retrieved via store.getState()...), then some profile names are displaying names that were fetched and some are holding "loading..." and are stuck forever. It makes sense. The correct data is retrieved from redux store and we have no subscribtions.
When I export the other version, created with useSelector, I always get its initial state. I never receive any user name or the value indicating "loading".
I have read many answers on StackOverflow. Some common mistakes include:
Some are saying your component is not getting updated. It's not the case, I tested it placing console.log to the code and adding the versionControl variable (see in the code) to make sure it updates.
Some answers are saying you don't update the store with reducers correctly and it still holds the same object. It's not the case, I tried both approaches, to return a fresh new object {...state,entities:{...state.entities...etc...}} and mutating the existing proxy object - both way my reducers should provide a new object and redux should notify changes.
Sometimes multiple store instances are created and things are messed. It's definitely not the case, I have a single call to configureStore() and a single component.
Also I don't see hook rules violation in my code. I have an if statement inside the useSelector fn but the useSelector hook itself is called unconditionally.
I have no idea what other reasons are causing useSelect to simply not work. Could anyone help me understand?
Ops, as usual, very simple typo is the reason. So many hours spent. Very sorry to those who have spent time trying to look at this and thanks for your time.
useSelector( state => !!state.profile.entities[userId] ? state.profile.entities[userId].loadingState : 'idle' )
There should be not .loadingState but .state. That's it.

How to share redux state client-side and props server-side in Next JS

I'm a newbie with Next JS.
I use Next JS and Redux.
I have a short code below:
const AdminContainer = (props) => {
return (
<AdminMasterView>
<DashboardView studentList={props.studentListServer}/>
</AdminMasterView>
)
}
export const getStaticProps = (async () => {
let response = await db.getInstance().query('SELECT * FROM student_register;');
return {
props: {
studentListServer: response
}, // will be passed to the page component as props
}
})
const mapStateToProps = state => ({
studentList: state.studentInfoReducers.studentList
});
const mapDispatchToProps = {
getStudentRegisterAction
};
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer);
I also have studentList (array type) props is declare in Redux. I want to use it to pass data because I have many tasks to do with data such as filter, order,...
Is there any way to use studentList like this and my app still is server rendering first time.
If I dispatch studentListServer to studentList, it still work. But my app isn't server rendering.
<DashboardView studentList={props.studentList}/>
Or easier, I'll check to use props.studentList for client-side and props.studentListServer for server-side. But I think it's not good.
Thank you so much!
You could use the next-redux-wrapper package. It allows to sync a Redux state on server and client. Consider the example:
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
let response = await db.getInstance().query('SELECT * FROM student_register;');
// dispatch the action that saves the data
store.dispatch({ type: 'SET_STUDENTS', payload: response });
return {
props: {
studentListServer: response
}, // will be passed to the page component as props
}
})
wrapper.getStaticProps wraps your getStaticProps function with the new parameter store that is a Redux store in fact.
Action with type SET_STUDENTS sets the student list on a server side. When Next.js generates the page, it will save this data in static JSON. So when the page opens on client side, next-redux-wrapper recreates a state dispatching HYDRATE action with saved on a build time static JSON that you can use to restore the studentInfoReducers reducer.
E.g. in your reducer you should implement something like:
import { HYDRATE } from 'next-redux-wrapper';
const initialState = { studentList: [] };
// studentInfoReducers reducer
function reducer(state = initialState, action) {
// this sets your student list
if (action.type === 'SET_STUDENTS') {
return {
...state,
studentList: action.payload,
};
}
// this rehydrates your store from server on a client
if (action.type === HYDRATE) {
return action.payload.studentInfoReducers;
}
return state;
}
So afterwards you should have a valid synced state on client and server at the same time:
const mapStateToProps = state => ({
studentList: state.studentInfoReducers.studentList // works on server and client
});
Let me know if you have any questions, next-redux-wrapper can be tricky from a first look.
You don't need to use Redux for that.
Using just cookies you can achieve bidirectional communication, see https://maxschmitt.me/posts/next-js-cookies/
Another example:
Client to Server: manually set a cookie in the client side and then read it in the server with req.headers.cookie or some library like 'cookie'
Server to Client: just read the cookie, and return what you need as a regular prop or update the cookie.
import { useState, useEffect } from "react";
import Cookie from "js-cookie";
import { parseCookies } from "../lib/parseCookies";
const Index = ({ initialRememberValue = true }) => {
const [rememberMe, setRememberMe] = useState(() =>
JSON.parse(initialRememberValue)
);
useEffect(() => {
//save/create the cookie with the value in the client
Cookie.set("rememberMe", JSON.stringify(rememberMe));
}, [rememberMe]);
return (
<div>
remember me
<input
type="checkbox"
value={rememberMe}
checked={rememberMe}
onChange={e => setRememberMe(e.target.checked)}
/>
</div>
);
};
Index.getInitialProps = ({ req }) => {
//read the cookie on the server
const cookies = parseCookies(req); //parseCookies is a simple custom function you can find
return {
//send the value as a regular prop
initialRememberValue: cookies.rememberMe
};
};
export default Index;
Reference: https://github.com/benawad/nextjs-persist-state-with-cookie/blob/master/pages/index.js

Fetching Data using React Hooks and Redux

I'm currently trying to fetch data from my back end using the following. I'm looking to destructure each event item to an eventIndexItem component, but events remains undefined after useEffect.
import React, { useState, useEffect } from 'react'
import EventIndexItem from './event_index_item'
import 'react-modern-calendar-datepicker/lib/DatePicker.css';
import { Calendar, utils } from 'react-modern-calendar-datepicker';
const EventIndex = ({ searchValue, fetchEvents }) => {
let today = utils().getToday()
const [ selectedDay, setSelectedDay ] = useState(today)
function dateFormatter (selectedDay) {
return selectedDay.month + " " + selectedDay.day
}
let formattedDate = dateFormatter(selectedDay)
let events
useEffect (() => {
events = fetchEvents()
}, [] )
function handleEvents () {
const filterEvents = events.filter(event => {
let title = event.title
if ( searchValue === "" || title.includes(searchValue) ) {
return true
} else {
return false
}
})
return filterEvents.map(event => (
<EventIndexItem
key={event.id}
event={event}
/>
))
}
return (
<div className='event-index-container'>
<div className='event-index-left'>
<h1 className="event-index-date"> { formattedDate } </h1>
{ handleEvents() }
</div>
<div className='event-index-right'>
<Calendar
calendarClassName="event-index-calendar"
value={selectedDay}
onChange={setSelectedDay}
colorPrimary={'#00a2c7'}
/>
</div>
</div>
)
}
export default EventIndex
Redux container looks like so.
import { connect } from 'react-redux'
import EventIndex from './event_index'
import { fetchEvents } from '../../action/event_actions'
const msp = (state, ownProps) => {
let searchValue = ownProps.searchValue
return ({
events: Object.values(state.entities.events),
searchValue,
currentUser: state.entities.users[state.session.id]
})
}
const mdp = dispatch => {
return ({
fetchEvents: () => dispatch(fetchEvents())
})
}
export default connect (msp, mdp) (EventIndex)
Action / Thunks
import * as EventAPIUtil from '../util/event_api_util';
import * as RSVPApiUtil from '../util/rsvp_api_util';
export const RECEIVE_EVENTS = "RECEIVE_EVENTS";
export const RECEIVE_EVENT = "RECEIVE_EVENT";
export const REMOVE_EVENT = "REMOVE_EVENT";
const receiveEvents = ( events ) => {
return ({
type: RECEIVE_EVENTS,
events
});
};
export const fetchEvents = () => (dispatch) => (
EventAPIUtil.fetchEvents().then(events => dispatch(receiveEvents(events)))
);
Reducers
import {
RECEIVE_EVENTS,
RECEIVE_EVENT,
REMOVE_EVENT
} from '../action/event_actions';
const eventsReducer = (state = {}, action) => {
Object.freeze(state);
let newState;
switch (action.type) {
case RECEIVE_EVENTS:
return action.events;
case RECEIVE_EVENT:
newState = Object.assign({}, state, { [action.event.id]: action.event });
return newState;
case REMOVE_EVENT:
newState = Object.assign({}, state)
delete newState[action.eventId]
return newState;
default:
return state;
}
};
export default eventsReducer;
I've previously created a class component calling this.props.fetchEvents() in componentDidMount and then invoking my handleEvents() in render with no issue. However, the variable events is undefined, but I do see events being added to redux state via redux-logger. Please advise! Also feel free to critique current code as well.
There are two approaches to this problem. You can either:
Fetch from React component (don't recommend)
To do so you will need to do some refactoring to your code
All API call functions should be wrapped in a Promise.
You'll have to add the results to the Redux state through an action dispatch
You'll use async callback function in useEffect
You'll need to wrap the fetch in a conditional to check if values already exist in the state.
Why I don't recommend:
Because you are mixing between business state and UI state. Redux helps separate this by managing the business state in Redux and the UI state in React. So always try avoiding mixing them.
Dispatch an async action from Redux (recommended)
Use Redux thunk for async actions.
dispatch the action from useEffect
make the API call from the reducer.
make your conditions in the reducer before the call.
Note that you must set conditions to make the API call and update the state or else the React component will keep re-rendering.
Why I recommend:
It separates business state from UI state.
One way for implementation of the recommended approach.
createSlice For auto-generating action creators, Reducer and selectors.
createAsyncThunk For working with API in Redux.
React Redux template Offical template, use as a setup example.

Best way to fetch data from a REST api using react hooks and context for state management?

I am trying out state management with react hooks and the context API. I have implemented a reducer pattern following some code from a todo app, but now I want to starting fetching data regularly from an API (e.g. implementing an infinite scroll), and I'm not sure now where the best place in the code is to make these async-REST-api calls.
I'm used to using a redux middleware library like redux-observable, redux-thunk, etc. for asynchronous tasks. But now that I'm not using redux, it's not clear to me what the best way is to do async updates. I suppose I could use await-promise reducers, but that doesn't feel right.
Any suggestions? (Having implemented a reducer pattern, I'm tempted to just fall back to a full redux-with-redux-obnservable implementation, though I was hoping context would slim down all that boilerplate.)
This is probably how I would implement it. I have a standard reducer. I will also create a helper functional component to help me set up the value for my context provider.
I also made some comments in the source code. I hope the following code snippet is simple enough to follow.
import React, { useReducer, useEffect, createContext } from 'react';
import FetchService from './util/FetchService'; // some helper functions
const OrderInfoContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case 'init':
return {};
case 'changeData':
return action.payload;
default:
return state;
}
};
const changeData = data => ({
type: 'changeData',
payload: data
});
/**
* This is a helper component that generate the Provider wrapper
*/
function OrderInfoProvider(props) {
// We will persist API payload in the state so we can assign it to the Context
const [orders, dispatch] = useReducer(reducer, {});
// We use useEffect to make API calls.
useEffect(() => {
async function fetchData() {
/**
* This is just a helper to fetch data from endpoints. It can be done using
* axios or similar libraries
*/
const orders = await FetchService
.get('/api/orders');
dispatch(changeData(orders))
}
fetchData();
}, []);
/**
* we create a global object that is available to every child components
*/
return <OrderInfoContext.Provider value={[orders, dispatch]} {...props} />;
}
// Helper function to get Context
function useOrderInfo() {
const context = useContext(OrderInfoContext);
if (!context) {
throw new Error('useOrderInfo must be used within a OrderInfoProvider');
}
return context;
}
export { OrderInfoProvider, useOrderInfo , changeData };
Here is an example that uses context and useReducer hook to set an app state and a context provider for state and dispatch.
The container uses useContext to get the state and the dispatch function, useEffect to do side effects like you'd use thunk, saga or middleware if you were using redux, useMemo to map state to props and useCallback to map each auto dispatched action to props (I assume you are familiar with react redux connect.
import React, {
useEffect,
useContext,
useReducer,
useCallback,
useMemo,
} from 'react';
//store provider
const Store = React.createContext();
const initStoreProvider = (rootReducer, initialState) => ({
children,
}) => {
const [state, dispatch] = useReducer(
rootReducer,
initialState
);
return (
<Store.Provider value={{ state, dispatch }}>
{children}
</Store.Provider>
);
};
//container for component
const ComponentContainer = ({ id }) => {
const { state, dispatch } = useContext(Store);
const num = state.find((n, index) => index === id);
//side effects, asynchonously add another one if num%5===0
//this is your redux thunk
const addAsync = num % 5 === 0;
useEffect(() => {
if (addAsync)
Promise.resolve().then(dispatch({ type: 'add', id }));
}, [addAsync, dispatch, id]);
//use callback so function does not needlessly change and would
//trigger render in Component. This is mapDispatch but only for
//one function, if you have more than one then use
//useCallback for each one
const add = useCallback(
() => dispatch({ type: 'add', id }),
[dispatch, id]
);
//This is your memoized mapStateToProps
const props = useMemo(() => ({ counter: num, id }), [
num,
id,
]);
return (
<Component add={add} doNothing={dispatch} {...props} />
);
};
//use React.memo(Component) to avoid unnecessary renders
const Component = React.memo(
({ id, add, doNothing, counter }) =>
console.log('render in component', id) || (
<div>
<button onClick={add}>{counter}</button>
<button onClick={doNothing}>do nothing</button>
</div>
)
);
//initialize the store provider with root reducer and initial state
const StoreProvider = initStoreProvider(
(state, action) =>
action.type === 'add'
? state.map((n, index) =>
index === action.id ? n + 1 : n
)
: state,
[1, 8]
);
//using the store provider
export default () => (
<StoreProvider>
<ComponentContainer id={0} />
<ComponentContainer id={1} />
</StoreProvider>
);
Example is here
https://resthooks.io/ uses the flux pattern just like you want, which allows things like middlwares, debuggability, etc. However, instead of having to write thousands of lines of state management, you just need a simple declarative data definition.
const getTodo = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
});
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(getTodo, { id });
return <div>{todo.title}</div>;
}

Resources