I'm struggling with the re-rendering issue in the SolidJS application. I have two routes, Home
and Detail
. A user can explore items in Home
, and click the link on the item name to switch a page to Detail
to check out detailed information.
export default function Home() {
const [items, setItems] = createSignal<Item[]>([]);
onMount(async () => {
setItems(
await fetchItemsThroughExpensiveAPI()
);
});
return (
<main>
<For each={items()}>
{(item) => (
<A href={`/item/${item.id}`}>{item.name}</A>
)}
</For>
</main>
);
}
export default function Detail() {
const params = useParams<{ id: string }>();
return (
<main>
// Some detailed information for the item ...
</main>
);
}
At this point, the API(fetchItemsThroughExpensiveAPI
) will be called back when the user returns to the Home
from Detail
. I'm expecting this it is caused by re-rendering. How do I prevent re-rendering Home
whenever a user returns to Home
from another page to avoid unnecessary API calls?
Use a resource to fetch the data outside the Home
component. If you need to fetch the data once during application's life, cache it.
https://www.solidjs.com/docs/latest/api#createresource
Lets make it more clear. There are different patterns to render async data, data that resides in a remote location.
ComponentA
below uses this pattern. Whenever it is re-rendered, data will be re-fetched.
Resource API is build to make use of this pattern.
ComponentB
below uses this pattern. Since data is fetched outside the component, re-rendering has no effect on it.
import { Accessor, Component, createSignal, Match, Switch } from 'solid-js';
import { render } from 'solid-js/web';
interface State { status: 'pending' | 'resolved' | 'rejected', data?: any, error?: any };
function getData(): Accessor<State> {
const [state, setState] = createSignal<State>({ status: 'pending' });
setTimeout(() => {
setState({ status: 'resolved', data: { name: 'John Doe', age: 30 } });
}, 1000);
return state;
};
const ComponentA = () => {
const state = getData();
return (
<Switch fallback={<div>Not Found</div>}>
<Match when={state().status === 'pending'}>
Loading...
</Match>
<Match when={state().status === 'resolved'}>
{JSON.stringify(state().data)}
</Match>
<Match when={state().status === 'rejected'}>
{JSON.stringify(state().error)}
</Match>
</Switch>
);
};
const ComponentB: Component<{ state: Accessor<State> }> = (props) => {
return (
<Switch fallback={<div>Not Found</div>}>
<Match when={props.state().status === 'pending'}>
Loading...
</Match>
<Match when={props.state().status === 'resolved'}>
{JSON.stringify(props.state().data)}
</Match>
<Match when={props.state().status === 'rejected'}>
{JSON.stringify(props.state().error)}
</Match>
</Switch>
);
};
const App = () => {
const state = getData();
const [show, setShow] = createSignal(false);
const handleClick = () => setShow(prev => !prev);
return (
<div>
{show() && (<ComponentA />)}
{show() && (<ComponentB state={state} />)}
<div><button onclick={handleClick}>Toggle Show Components</button></div>
</div>
)
};
render(() => <App />, document.body);
Here you can see it in action: https://playground.solidjs.com/anonymous/32518df5-9840-48ea-bc03-87f26fecc0f4
Here we simulated an async request using setTimeout. This is very crude implementation to prove a point. If you are going to fetch a remote resource, you should use the Resource API which provides several utilities like automatic re-fecthing when request parameters change.
There are few issues with your implementation. First and foremost, async data in never guaranteed to be received, so you should handle failures.
Since it takes some time to receive the remote data, you have to show the user some indicator of the ongoing request, like a loader.
If API call is an expensive operation, you have to cache the result, rather than forcing the UI not to re-render. In this sense, your approach is very problematic.