NextJS SSR and Client side state - reactjs

I have the following nextJS app:
export default function Home({ productsData }) {
const [user, setUser] = useState(null);
const [products, setProducts] = useState([]);
useEffect(() => {
if (productsData) setProducts(productsData);
}, [productsData]);
useEffect(() => {
const userLocal = window.localStorage.getItem("user");
if (userLocal) {
setUser(JSON.parse(userLocal));
}
}, []);
return (
<div className="container">
<ul className="row">
{products.map((product) => {
return (
<h1>
{product.translation.name} -{" "}
{user
? user.premium
? product.prices.premium
: product.prices.price
: product.prices.price}
</h1>
);
})}
</ul>
</div>
);
}
export async function getServerSideProps() {
const data = [
{
prices: {
premium: 25,
price: 59.95,
},
translation: {
name: "Product 1",
},
},
{
prices: {
premium: 25,
price: 29.95,
},
translation: {
name: "Product 2",
},
},
];
return {
props: {
productsData: data,
},
};
}
This works but if I do a "curl" request to localhost I dont see that the server is rendering anything, that is because useEffect setting "products" happen on the Client side.
But if I do this:
const [products, setProducts] = useState(productsData);
Then I have this error:
Error: Hydration failed because the initial UI does not match what was rendered on the server. - Buscar con Google
So, do I have to choose between SSR and having the state in the client side?
I tried const [products, setProducts] = useState(productsData);

you dont need useEffect this time !
you can use Loading for this
export default function Home({ productsData }) {
const [user, setUser] = useState(null);
useEffect(() => {
const userLocal = window.localStorage.getItem("user");
if (userLocal) {
setUser(JSON.parse(userLocal));
}
}, []);
return (
<div className="container">
<ul className="row">
{productsData?.map((product) => {
return (
<h1>
{product.translation.name} -{" "}
{user
? user.premium
? product.prices.premium
: product.prices.price
: product.prices.price}
</h1>
);
})}
</ul>
</div>
);
}
export async function getServerSideProps() {
const data = [
{
prices: {
premium: 25,
price: 59.95,
},
translation: {
name: "Product 1",
},
},
{
prices: {
premium: 25,
price: 29.95,
},
translation: {
name: "Product 2",
},
},
];
return {
props: {
productsData: data,
},
};
}

I strongly recommend TanStack Query (formerly ReactQuery) for this kind of scenario.
You can take advantage of SSR, client fetching, etc. TanStack Query is just a cache layer between NextJS and your data.
They have a SSR guide with NextJS in their docs.

Related

React/Firebase. How can i filter some products by categories using firebase?

How can i filter some products by categories using firebase? This is a fragment of my code
Not sure if you have a correct db.json file, i had to flatMap the result but here is a working code. I used require to load you json file and left const [products, setProducts] = useState([]); just in case. Also i switched categories to useMemo so this variable will not update on each re-render.
import React, { useState, useEffect, useMemo } from "react";
import "./styles.scss";
import { Link } from "react-router-dom";
const dbProducs = require("./db.json");
const CategoriesPage = () => {
// const {product} = useContext(Context)
const [products, setProducts] = useState([]);
const categories = useMemo(() => {
return [
{ id: 1, title: "Tablets" },
{ id: 2, title: "Computers" },
{ id: 3, title: "Consoles" },
{ id: 4, title: "Photo and video" },
{ id: 5, title: "Technics" },
{ id: 6, title: "Game Content" },
{ id: 7, title: "Notebooks" },
{ id: 8, title: "Smartphones" },
{ id: 9, title: "Headphones" },
{ id: 10, title: "Steam" }
// {id: 11,imageSrc:steamcards, title: 'Стиральные машины'},
// {id: 12,imageSrc: coffeemaschine, title: 'One stars'},
// {id: 13,imageSrc:headphones, title: 'Холодильники'},
];
}, []);
useEffect(() => {
const flatMapped = dbProducs.flatMap((x) => x.products);
setProducts(flatMapped);
}, []);
return (
<section className="popular__categories">
<h3 className="events__title">
<span>Categories</span>
</h3>
<div className="categories__wrapper">
{categories.map((category) => (
<Link
to={`${category.id}`}
className="categories__content"
key={category.id}
>
<h2 className="categories__title">{category.title}</h2>
<img
className="categories__img"
alt={category.title}
src={category.imageSrc}
/>
<ul>
{products
.filter((p) => p.category === category.title)
.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</Link>
))}
</div>
</section>
);
};
export default CategoriesPage;
Technically it would be better to clone and extend your categories objects with additional array property with useMemo, or you can add additional Map object with key = Category(title) and value = products (filtered) but it is up to you.
Full example with Context, Routes, Navigation:

React mock asynchronous axios with jest doesn't work

I'm trying to test the component below using mock axios, however, it looks like the components are not rendered as expected, could someone help me on that? I have been stuck for quite a while. The component is fetching an api every 1 second.
const RealtimePrice = () => {
var [cryptoFeed, setCryptoFeed] = useState<cryptoFeed>([]);
var [currency, setCurrency] = useState(currencyList[0]);
var [cryptoSearch, setCryptoSearch] = useState("");
const url = `https://api.coingecko.com/api/v3/coins/markets?ids=${ids}&vs_currency=${currency}`;
const intervalRef = useRef<NodeJS.Timer>();
const onCurrencyChangeHandler = useCallback((newValue: string) => {
setCurrency(newValue);
}, []);
const onCryptoSearchChangeHandler = useCallback((newValue: string) => {
setCryptoSearch(newValue);
}, []);
useEffect(() => {
const getCryptoFeed = () => {
axios.get(url).then((response: any) => {
if (response.data) {
console.debug("The state is set");
setCryptoFeed(response.data);
} else {
console.debug("The state is not set");
setCryptoFeed([]);
}
});
};
getCryptoFeed();
intervalRef.current = setInterval(getCryptoFeed, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, [url]);
const priceBlocks = cryptoFeed
.filter((crypto) =>
crypto.name.toLowerCase().includes(cryptoSearch.toLowerCase())
)
.map((crypto: any) => {
return (
<PriceBlock
key={crypto.id}
id={crypto.id}
name={crypto.name}
price={crypto.current_price}
volume={crypto.total_volume}
change={crypto.price_change_24h}
></PriceBlock>
);
});
return (
<div className={styles.container}>
<div className={styles["header-section"]}>
<h1>Cryptocurrency Realtime Price</h1>
<div className="input-group">
<Selectbox
onChange={onCurrencyChangeHandler}
defaultOption={currencyList[0]}
options={currencyList}
/>
<Inputbox
placeHolder="Enter crypto name"
onChange={onCryptoSearchChangeHandler}
/>
</div>
</div>
<div className={styles.priceblocks}>{priceBlocks}</div>
</div>
);
};
The test is the defined as the following, findByText gives error, it couldn't find the element.
import { render, screen } from "#testing-library/react";
import RealtimePrice from "../RealtimePrice";
describe("Realtime Price", () => {
it("should render the Bitcoin price block", async () => {
render(<RealtimePrice />);
const pb = await screen.findByText("Bitcoin");
expect(pb).toBeInTheDocument();
});
});
And in package.json I have set
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}"
],
"resetMocks": false
}
In src/mocks/axios.js
const mockGetResponse = [
{
id: "bitcoin",
name: "Bitcoin",
price: 20000,
volume: 12004041094,
change: -12241,
},
{
id: "solana",
name: "Solana",
price: 87,
volume: 200876648,
change: 122,
},
];
const mockResponse = {
get: jest.fn().mockResolvedValue(mockGetResponse),
};
export default mockResponse;
With our comments seems clear the issue is that mock is not returning a proper response.data (that's why u are setting an empty array as the state)
Try doing:
const mockResponse = {
get: jest.fn().mockResolvedValue({data: mockGetResponse}),
};

How to display data with reactJs?

I am using ReactJs with Laravel as an API. I Want to display some data but nothing worked for me.
This is the result of the API:
{
"user_id": 2,
"id": 1,
"chat": [
{
"sender_id": 3,
"message": "Hi"
},
{
"sender_id": 4,
"message": "Hello"
}
]
}
When I try to display user_id, it displayed it but the other part, the chat, didn't show up.
This is how I consume the API:
const [chatsData, setChats] = useState([]);
// ** Renders Chat
useEffect(() => {
getChats()
}, []);
const getChats = async () => {
const response = await axios.get(`${API_ENDPOINT}/api/listChats`);
setChats(response.data);
}
<ul className='chat-users-list chat-list media-list'>
{chatsData.map((item) => {
return (
<>
<Avatar img={item?.senders?.user_image} imgHeight='42' imgWidth='42' />
<li><h5>{item.chat?.message}</h5></li>
</>
)
})}
</ul>
I will be very thankful if anyone could help me.
You just have to loop on the chat field like:
import React from 'react';
export default function App() {
const response = {
user_id: 2,
id: 1,
chat: [
{
sender_id: 3,
message: 'Hi',
},
{
sender_id: 4,
message: 'Hello',
},
],
};
return (
<div>
<ul className="chat-users-list chat-list media-list">
{response.chat?.map((item) => {
return (
<>
<li>
<h5>
{item.sender_id} : {item?.message}
</h5>
</li>
</>
);
})}
</ul>
</div>
);
}
Code example here

How to call api from each of the element rendered

I wonder if calling api for every element rendering is possible. The code below didn't work for me.
export default function App() {
const items = [
{ title: 1, description: "description1" },
{ title: 2, description: "description2" }
];
const finalTitleByApi = async (title) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${title}`
).then((response) => response.json());
return response;
};
return (
<div>
{items.map((item) => {
return (
<p>
{finalTitleByApi(item.title).title}
</p>
);
})}
</div>
);
}
What is wrong with the code above. Any help is will be appreciated. Thank you.
This is the example codesandbox link https://codesandbox.io/s/for-each-rendered-element-that-calls-api-pmcnn6?file=/src/App.js:879-886
Try to use react-async library , hope will be helpful react-async
To trigger the async function use useEffect to invoke it during initial rendering of the component as follows. Additionally, you can use a state to manage it as well.
const [responseState, setResponseState] = useState(null);
const finalTitleByApi = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
).then((response) => response.json());
console.log("Response: ", response);
setResponseState(response);
};
useEffect(() => {
finalTitleByApi();
}, []);
useEffect(() => {
console.log("Response State: ", responseState);
}, [responseState]);
One solution that I think is
import React, { useEffect } from "react";
import "./styles.css";
import {useApplicationContext} from './Context';
export default function App() {
const {titles, setTitles} = useApplicationContext();
const items = [
{ title: "1", description: "description1" },
{ title: "5", description: "description2" },
{ title: "8", description: "description2" },
{ title: "9", description: "description2" },
{ title: "10", description: "description2" },
{ title: "24", description: "description2" }
];
const makeDivs = () => {
let a = {};
items.map(async (item) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${item.title}`
).then(response => response.json());
a[item.title] = res.title;
setTitles((prevState) => {
return {...a}
});
})
}
React.useEffect(()=> {
makeDivs()
}, [])
// console.log(a )
return (
<div>
{JSON.stringify(titles)}
{items.map((item, index) => {
return (
<p
key={Math.random()}
style={{
color: "black",
backgroundColor: "yellow",
height: 400,
width: 400
}}
>
<span>index: {item.title} {titles && titles[item.title]}</span>
</p>
);
})}
</div>
);
}
used Context provider for not re-render component state
link of sandbox is Sandbox

Multiple useEffect and setState causing callback to be called twice

I'm test driving a pattern I found online known as meiosis as an alternative to Redux using event streams. The concept is simple, the state is produced as a stream of update functions using the scan method to evaluate the function against the current state and return the new state. It works great in all of my test cases but when I use it with react every action is called twice. You can see the entire app and reproduce the issue at CodeSandbox.
import state$, { actions } from "./meiosis";
const App = () => {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({
title: "",
status: "PENDING"
});
useEffect(() => {
state$
.pipe(
map(state => {
return state.get("todos")
}),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setTodos(state));
}, []);
useEffect(() => {
state$
.pipe(
map(state => state.get("todo")),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setNewTodo(state));
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{genList(todos)}
<div className="formGroup">
<input
type="text"
value={newTodo.title}
onChange={evt => actions.typeNewTodoTitle(evt.target.value)}
/>
<button
onClick = {() => {
actions.addTodo()
}}
>
Add TODO
</button>
<button
onClick={() => {
actions.undo();
}}
>UNDO</button>
</div>
</header>
</div>
);
};
Meisos
import { List, Record } from "immutable";
import { Subject } from "rxjs";
const model = {
initial: {
todo: Record({
title: "",
status: "PENDING"
})(),
todos: List([Record({ title: "Learn Meiosis", status: "PENDING" })()])
},
actions(update) {
return {
addTodo: (title, status = "PENDING") => {
update.next(state => {
console.log(title);
if (!title) {
title = state.get("todo").get("title");
}
const todo = Record({ title, status })();
return state.set("todos", state.get("todos").push(todo));
});
},
typeNewTodoTitle: (title, status = "PENDING") => {
update.next(state => {
return state.set("todo", Record({ title, status })())
});
},
resetTodo: () => {
update.next(state =>
state.set("todo", Record({ title: "", status: "PENDING" })())
);
},
removeTodo: i => {
update.next(state => state.set("todos", state.get("todos").remove(i)));
}
};
}
}
const update$ = new BehaviorSubject(state => state) // identity function to produce initial state
export const actions = model.actions(update$);
export default update$;
Solve my problem. It stemmed from a misunderstanding of how RXJS was working. An issue on the RxJS github page gave me the answer. Each subscriptions causes the observable pipeline to be re-evaluated. By adding the share operator to the pipeline it resolves this behavior.
export default update$.pipe(
scan(
(state, updater) =>
updater(state),
Record(initial)()
),
share()
);

Resources