Fresh Call To API EndPoint on Button Click in React JS - reactjs

I haven API endpoint, that gives me a random text, on each call. As of now, when the React Component loads for the first time, a call goes through to the API and I get the random text.
The code looks like this. I am using redux for state management.
const RandomQuoteList = ({ todo, isLoading, startLoadingTodos }) => {
useEffect(() => {
startLoadingTodos();
}, []);
const [inputValue, setInputValue] = useState(`HelloThere`);
function changeRandomText(e) {
// const item = e.target.value;
var something = Math.random();
console.log(`changeRandomText clicked + ${something}`);
setInputValue(`changeRandomText clicked + ${something}`);
console.log(inputValue);
}
const loadingMessage = <div>Loading todos...</div>;
const content = (
<GeneralWrapper>
<RandomQuoteItem todo = {todo} inputValue = {inputValue}/>
<Button onClick={changeRandomText}>Get A New Quote</Button>
</GeneralWrapper>
);
return isLoading ? loadingMessage : content;
};
const mapStateToProps = state => ({
isLoading: getTodosLoading(state),
todo: getTodos(state),
});
const mapDispatchToProps = dispatch => ({
startLoadingTodos: () => dispatch(loadTodos()),
});
export default connect(mapStateToProps, mapDispatchToProps)(RandomQuoteList);
Now, I want to be able to use a simple button click to 'refresh' the API call. That way, the API endpoint will be triggered and a fresh new text will get updated.
I have looked at the following stack over flow questions.
React: re render componet after button click
How to refresh React page/component on Button click after POST
ReactJs : How to reload a component onClick
But, I am not getting far. I am able to randomly change the state of a text component, and that is changing the text component. So, I have the random value change part taken care of.
The target component looks something like this. When I click the button on the above component, the below component updates the random text no problem.
const RandomQuoteItem = ({ todo,inputValue }) => {
//set the style for the display.
// const Container = todo.isCompleted ? TodoItemContainer : TodoItemContainerWithWarning;
const Container = TodoItemContainer;
return (
<Container>
{/* this is where you show your API response single items. */}
<h3>{todo.quoteContent}</h3>
<h4>{todo.quoteAuthor}</h4>
<h4>{todo.quoteIdentifierString}</h4>
<p>-------------------------------</p>
<h4>{todo.quoteIdentifierCompadre}</h4>
<h4>{todo.dateTimeOfResponse}</h4>
<h4>{todo.detailsAboutOperation}</h4>
<p>{inputValue}</p>
</Container>
);
}
Now, how do I link this random state change to my RandomQuoteItem state, so, it makes fresh data call?

Based on the comment from rahuuz above, I ended up with this. and it worked.
function changeRandomText(e) {
// const item = e.target.value;
var something = Math.random();
console.log(`changeRandomText clicked + ${something}`);
setInputValue(`changeRandomText clicked + ${something}`);
console.log(inputValue);
startLoadingTodos(); //this specific line solved the problem.
}
I think, it worked in my favour that I already had redux and reducers and all of that hooked up. If that was no the case, this specific solution may not have worked, I think.
Button click calls the startLoadingTodos function, which in turn calls the API and that returns data, updating the redux state, and component also updates.

Related

How to make setting state with useEffect() to run on page refresh?

My code is not long or complicated at all. It's simple. so please read!
(Im using react + next.js)
In the root file, app.js, I have useEffect to fetch photo data. This data array will be used in a page component so I pass it down from app.js via <Component.../>
function MyApp({ Component, pageProps }) {
const [photoData, setPhotoData] = useState([]);
const [user, setUser] = useState([]);
useEffect(() => {
const getPhotos = async () => {
try {
const photoData = await axios.get(
"https://jsonplaceholder.typicode.com/albums"
);
setPhotoData(photoData.data);
} catch (error) {
console.log(error);
}
};
getPhotos();
}, []);
useEffect(() => {
//code for finding user. no external api used.
setUser(user);
}
}
}, []);
const passedProps = {
...pageProps,
photoData,
user
};
return (
...
<Component {...passedProps} />
)
Then I pass the data (photodata) from a Home component to a (app.js 's) grandchild component, an Photo component
export default function Home({ photoData, user }) {
return(
<Photo photoData={photoData} user={user} />
)
In Photo component, I am receiving photoData and trying to set a state for photoArr with the default state of photoData.
When the entire app is first loaded, the photoData is passed down to the Photo component successfully that it sets the state without any issue.
But the main problem is that when I am in the Photo page (photos are loaded) and refresh the page, then it does not set the state for photoArr with photoData. Even though I can console log photoData received from app.js, it does not set state, photoArr, with the default state, photoData.
export default function Photo({ photoData, user }) {
const [photoArr, setPhotoArr] = useState(photoData);
//I have this as state because I change this array
//later on in this component (doing CRUD on the photo array).
console.log(photoData); // this returns the array received from app.js
console.log(photoArr); // []. returns an empty array
console.log(user); // returns the user object received from app.js.
return (
<div>
{photoArr.length > 0 ?
.... code mapping photoArr
: "Data is empty" //It returns this comment when I refresh the page
}
</div>
)
As you can see above, when I refresh the page, I get "Data is empty" meaning photoArr was not set even with the given default state. If I keep refreshing the page multiple times, it still shows a blank page.
From my research, it's due to setting state being asynchronous? So then how can I fix this problem?
Try this:
(In your Photo page)
const [photoArr, setPhotoArr] = useState(null);
useEffect(() => {
if(photoData.length) setPhotoArr(photoData) // If not empty, set the Arr
},[photoData]} // We listen to photoData's change
On page load, there aren't any data in your photoData, and as it pass down to Photo component, react remembers that state.
But with useEffect listen to photoData's change, we can setPhotoArr once the getPhotos function got the data back.

Excessive rerendering when interacting with global state in React Context

I'm building a Chat app, I'm using ContextAPI to hold the state that I'll be needing to access from different unrelated components.
A lot of rerendering is happening because of the context, everytime I type a letter in the input all the components rerender, same when I toggle the RightBar which its state also resides in the context because I need to toggle it from a button in Navbar.
I tried to use memo on every components, still all the components rerender everytime I interact with state in context from any component.
I added my whole code simplified to this sandbox link : https://codesandbox.io/s/interesting-sky-fzmc6
And this is a deployed Netlify link : https://csb-fzmc6.netlify.app/
I tried to separate my code into some custom hooks like useChatSerice, useUsersService to simplify the code and make the actual components clean, I'll also appreciate any insight about how to better structure those hooks and where to put CRUD functions while avoiding the excessive rerendering.
I found some "solutions" indicating that using multiple contexts should help, but I can't figure out how to do this in my specific case, been stuck with this problem for a week.
EDIT :
The main problem here is a full rerender with every letter typed in the input.
The second, is the RightBar toggle button which also causes a full rerender.
Splitting the navbar and chat state into two separate React contexts is actually the recommended method from React. By nesting all the state into a new object reference anytime any single state updated it necessarily triggers a rerender of all consumers.
<ChatContext.Provider
value={{ // <-- new object reference each render
rightBarValue: [rightBarIsOpen, setRightBarIsOpen],
chatState: {
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue,
},
}}
>
{children}
</ChatContext.Provider>
I suggest carving rightBarValue and state setter into its own context.
NavBar context
const NavBarContext = createContext([false, () => {}]);
const NavBarProvider = ({ children }) => {
const [rightBarIsOpen, setRightBarIsOpen] = useState(true);
return (
<NavBarContext.Provider value={[rightBarIsOpen, setRightBarIsOpen]}>
{children}
</NavBarContext.Provider>
);
};
const useNavBar = () => useContext(NavBarContext);
Chat context
const ChatContext = createContext({
editValue: "",
setEditValue: () => {},
editingId: null,
setEditingId: () => {},
inputValue: "",
setInputValue: () => {}
});
const ChatProvider = ({ children }) => {
const [inputValue, setInputValue] = useState("");
const [editValue, setEditValue] = useState("");
const [editingId, setEditingId] = useState(null);
const chatState = useMemo(
() => ({
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue
}),
[editValue, inputValue, editingId]
);
return (
<ChatContext.Provider value={chatState}>{children}</ChatContext.Provider>
);
};
const useChat = () => {
return useContext(ChatContext);
};
MainContainer
const MainContainer = () => {
return (
<ChatProvider>
<NavBarProvider>
<Container>
<NavBar />
<ChatSection />
</Container>
</NavBarProvider>
</ChatProvider>
);
};
NavBar - use the useNavBar hook
const NavBar = () => {
const [rightBarIsOpen, setRightBarIsOpen] = useNavBar();
useEffect(() => {
console.log("NavBar rendered"); // <-- log when rendered
});
return (
<NavBarContainer>
<span>MY NAVBAR</span>
<button onClick={() => setRightBarIsOpen(!rightBarIsOpen)}>
TOGGLE RIGHT-BAR
</button>
</NavBarContainer>
);
};
Chat
const Chat = ({ chatLines }) => {
const { addMessage, updateMessage, deleteMessage } = useChatService();
const {
editValue,
setEditValue,
editingId,
setEditingId,
inputValue,
setInputValue
} = useChat();
useEffect(() => {
console.log("Chat rendered"); // <-- log when rendered
});
return (
...
);
};
When running the app notice now that "NavBar rendered" only logs when toggling the navbar, and "Chat rendered" only logs when typing in the chat text area.
I recommend use jotai or other state management libraries.
Context is not suitable for high-frequency changes.
And, the RightBar's state looks can separate to other hook/context.
There is tricky one solution solve some render problems:
https://codesandbox.io/s/stoic-mclaren-x6yfv?file=/src/context/ChatContext.js
Your code needs to be refactored, and useChatService in ChatSection also depends on your useChat, so ChatSection will re-render when the text changes.
It looks like you are changing a global context on input field data change. If your global context is defined on a level of parent components (in relation to your input component), then the parent and all children will have to re-render.
You have several options to avoid this behavior:
Use context on a lower level, e.g. by extracting your input field to an external component and using useContext hook there
Save the input to local state of a component and only sync it to the global context on blur or submit

Traversing to another page in reactjs inside an onclick button event

I have a simple reactjs button (Material-UI components) and I need to trigger its onclick event, inside that I call an API to the server and upon successful callback I need to transfer use to a new page, I am writing the onclick event function as follow but it doesn't work
import SignupPage from "views/SignupPage/SignupPage.js";
export default function MyPage(props) {
const [is_logged_in, set_is_logged_in] = useState(props.is_looged_in);
const [fname, set_fname] = useState(props.fname);
const [lname, set_lname] = useState(props.lname);
...
function goto(e){
axios.get('http://127.0.0.1:9000/api/is_logged_in/')
.then(res =>{
if (res.status!=200) {
throw new Error('Network response was not ok');}
return res;})
.then(res=>{
const value=res.data.res;
set_is_logged_in(value);
if (value==1){
//traverse to new page upon successful login
var uploadScreen=[];
uploadScreen.push(<SignupPage />);
}
})
}
...
<Button onClick={goto} color="primary">Evaluate</Button>
}
Can someone help to come up with correct codes?

Fetching w/ custom React hook based on router parameters

I'm trying to fetch data with a custom React hook, based on the current router parameters.
https://stackblitz.com/edit/react-fetch-router
What it should do:
On first load, check if URL contains an ID...
If it does, fetch a todo with that ID
If it does not, fetch a todo with a random ID & add ID to url
On fetch button clicks...
Fetch a todo with a random ID & add ID to url
What is wrong:
Watching the console or inspector network tab, you can see that it's firing several fetch requests on each click - why is this and how should this be done correctly?
Since you used history.push on handleClick, you will see multiple requests being sent as you are using history.push on click handler which will cause a re-render and make use use random generated url as well as also trigger the fetchTodo function.
Now a re-render will occur which is cause a randomised id to be generated. and passed onto useTodo hook which will lead to the fetchTodo function being called again.
The correct solution for you is to set the random todo id param on handleClick and also avoid unnecessary url updates
const Todos = () => {
const history = useHistory();
const { id: todoId } = useParams();
const { fetchTodo, todo, isFetching, error } = useTodo(todoId);
const isInitialRender = useRef(true);
const handleClick = () => {
const todoId = randomMax(100);
history.push(`/${todoId}`);
};
useEffect(() => {
history.push(`/${todoId}`);
}, []);
useEffect(() => {
if(!isInitialRender.current) {
fetchTodo();
} else {
isInitialRender.current = false
}
}, [todoId])
return (
<>
<button onClick={handleClick} style={{marginBottom: "10px"}}>Fetch a todo</button>
{isFetching ? (
<p>Fetching...</p>
) : (
<Todo todo={todo} color={todo.color} isFetching={isFetching} />
)
}
</>
);
};
export default Todos;
Working demo

Saving the count on a react effect

I'm trying to learn how to use react hooks with effects and state.
I have some text with up and down votes on it.
function Overview() {
const [countUp, setCountUp] = useState(0);
const [countDown, setCountDown] = useState(0);
useEffect(() => {
document.title = `You clicked ${countUp} times`;
}, [countUp]);
useEffect(() => {
document.title = `You clicked ${countDown} times`;
}, [countDown]);
return (
<List.Item
key="1"
title="whisperFunding"
description="test"
actions={[
<Button onClick={() => setCountUp(countUp + 1)}><IconText type="like-o" text={countUp} key="list-vertical-like-o" /></Button>,
<Button onClick={() => setCountDown(countDown + 1)}><IconText type="dislike-o" text={countDown} key="list-vertical-like-o" /></Button>,
]}
>
I'm trying to figure out how to save the state so that each time I refresh the page, the previous state is loaded instead of being reset to zero.
Do I need to make a form to do that?
Currently, the up and down clicks work, but if I refresh the page, everything goes back to zero. How do I save the state to preserve for the next time I visit the page?
Is there an example somewhere that I can follow of how to do this? I have plenty of forms in my app but not sure how to integrate one in my list item for the count? or if I can use effect to save the count history.
Here is a crude example of how you can achieve this with the browsers localStorage. You can read the browsers localStorage Here. Since React is a frontend framework you need to save the value somewhere or when you refresh the page the entire app will reload. So you can use a number of different storage devices like localStorage or a database for example. Saving to a database is more permanent and gives you more control because localStorage can be deleted by a user but they rarely do so. If you need a production app that remembers the count long term or you need this count to be more secure you may want to set up an api with nodeJs and mongoDB or something similar but for most cases in frontend frameworks localStorage can be enough and easy to implement. I just made a simple counter you can adjust it to your liking but this should give you a good starting point.
First I used a return statement in your setState functions to get the previous state. Then you can just calculate the count and set the localStorage then return the new count to the state object. To get the initial value from localStorage you can do useEffect with an empty dependency array to run the effect only once when the component first mounts. You can then get the localStorage item and set it to your count. Here is an example.
import React, { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
const increase = () => {
setCount(prevCount => {
const newCount = Number(prevCount) + 1;
localStorage.setItem("count", newCount);
return newCount;
});
};
const decrease = () => {
setCount(prevCount => {
const newCount = Number(prevCount) - 1;
localStorage.setItem("count", newCount);
return newCount;
});
};
useEffect(() => {
const initialValue = localStorage.getItem("count");
if (initialValue) setCount(initialValue);
}, []);
// Just to show you the localStorage Value
console.log(localStorage.getItem("count"));
return (
<div>
<button onClick={increase}>Plus</button>
<button onClick={decrease}>Minus</button>
<div>{count}</div>
</div>
);
};
export default Counter;

Resources