How to update redux store with react suspense - javascript

In following code, Sample uses a async function to get some data. I will be using that data to update the Redux store so any component can access the username within the application.
const resource = fetchData();
function Sample() {
// throws a promise when retrieving data
const name = resource.read();
const dispatch = useDispatch();
dispatch(setUsername(name));
const username = useSelector((state) => state.username);
return <div>User: {username}</div>;
}
<Suspense fallback="loading....">
<Sample />
</Suspense>
Here, lets assume there is no way my application can proceed without the username. Well, <Suspense> at parent level achieves that up until data are fetched from the resource. However, there is a slight gap from Redux event dispatch to <Sample> component re-render where is displays User: instead of loading.... (event don't force the re-render immediately so username is empty). So I will see following content in the web page in the same order
loading.... --> User: --> User: Srinesh
So my requirement is to show loading.... until store is updated. On the web page, I'm expecting to see,
loading.... --> User: Srinesh
Is there a way to achieve this without using another condition at <Sample> component level to show loading.... if the username is null?

The first issue is that dispatching in the middle of component rendering logic is a side effect, and you must never do that.
A safer place to put that would be in a useLayoutEffect hook, which will dispatch as soon as the component is done rendering, but force a synchronous re-render before the browser has a chance to paint. That way you won't see the flash.

Related

calling setState only once inside of useEffect--is there a better method?

In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.

React useEffect() hook highly affects SEO

I have a static website made with react that requests data from the backend in the useEffect() hook:
export default const App = () => {
const [data, setData] = useState("");
useEffect(() => {
server.get().then(data => {
setData(data)
})
})
return(
<title>{data}</title>
<h1>{data}</h1>
)
}
However, when Bing crawls the webpage, the following problem occurs:
Bing Screenshot:
<title></title>
<h1></h1>
How can I solve this issue?
React isn't used for static sites. If you'd like to have better SEO and server-side rendering you can use nextjs.
The way your app is setup currently will only return some HTML with and empty body to a GET request to / (which is what I suppose crawlers like the one you mentioned use) and starts rendering components after the JavaScript is loaded.
But if you decide on a server-side rendering approach, whenever a request is made to your app the server will first render the app on it's side and the return an HTML string with the rendered components.
Did you check if your server.get() is returning some data? I can't see any url here, so maybe it's actually returning nothing.
Even so, maybe you forgot to pass the second argument of useEffect, which is an array of arguments, which this hooks uses to trigger itself. For example, if you want to trigger only once, when component is mounted, you need to pass [] as second argument of useEffect.

React and Redux losing State

Hope you all are fine. I am new to react redux world. I am learning and working on a call logging project. I have a few questions and it would great if someone can guide me whether I am doing it wrong or tell me the alternative.
I am using JWT to authenticate a user. Once the user details are verified. I am dispatching success action and in the reducer, I am setting the state to authenticated true and the response. I am also storing the token and expiryTime in localStorage
In the root file which is index file. I am checking if the token exists in localStorage and if so then dispatching sign in action.
Everything is working. But I am losing other values like a response from a server. When he logged in for the first time. How can I tackle this problem ?
Secondly, there is a User initial icon on the top right corner. I get the initial when a user logs in and it gets stored in auth state. it works fine but once again if I refresh the page it becomes null and I lose that initial.
so I tried another way and stored the initial in localStorage. But the navbar already rendered on the screen and I don't see any initial until I refresh the page.
I have passed new key in mapStateToProps. ii and stored initial from localStorage in it and it working fine. Is this a valid way of doing it ???
Regards
Meet
const SignedInLinks = (props) => {
return (
<ul className="right">
<li><NavLink to="/signin" onClick=
{props.signOut}>Log Out</NavLink></li>
<li><NavLink className="btn btn-floating pink lighten-1" to="/">
{props.ii ? props.ii : null }
</NavLink></li>
</ul>
)}
const mapStateToProps = state => {
return {
auth: state.auth,
ii: window.localStorage.getItem('ui')
}
}
export default connect(mapStateToProps, { signOut })(SignedInLinks);
Rather than using localStorage in mapStateToProps, intialize your ii state in your reducer corresponding to that state and then pass it to your component via mapStateToProps. Something like this.
const iiReducer = (state = window.localStorage.getItem('ui') || false, action) => {
/*.
.
. Other Logic
.
.*/
return state
}
and then use it normally as you would from a store's state
const mapStateToProps = state => {
return {
auth: state.auth,
ii: state.ii
}
}
Hope this helps !
I believe I have an idea of what the problem is (I'm kind of a beginner in react and redux aswell, so tell me if I'm speaking nonsense).
You say that you store the token in localstorage (it is recommended not to do that btw, the recommended way is to store it in a cookie), and if a valid token is found in localstorage, you log in. I'm guessing that you store the response from the server (including the icon) in the app's state, or in the redux store? If that is the case, this information will be removed when you update the browser (f5),therefore not being loaded anymore.
The solution is to make sure to load the relevant data when the component mounts, so in your componentDidMount method (if you don't have one, make one), and set the state in that method.

React Router 4 navigation pattern in a Redux app

I am currently working on a simple React app with a very common workflow where users trigger Redux actions that, in turn, request data from an API. But since I would like to make the results of these actions persistent in the URL, I have opted for React Router v4 to help me with the job.
I have gone through the Redux integration notes in the React Router documentation but the idea of passing the history object to Redux actions just doesn't feel like the most elegant pattern to me. Since both Redux and Router state changes cause React components to be re-rendered, I'm a little worried the component updates could go a bit out of control in this scenario.
So in order to make the re-rendering a bit more predictable and sequential, I have come up with the following pattern that attempts to follow the single direction data flow principle:
Where I used to trigger Redux actions as a result of users' interactions with the UI, I am now calling React Router's props.history.push to update the URL instead. The actual change is about updating a URL parameter rather than the whole route but that's probably not that relevant here.
Before:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Fire a Redux action
this.props.setUser(userId)
}
After:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Use React Router to update the URL
this.props.history.push(`/user-selector/${userId}`)
}
The userId change in the URL causes React Router to trigger a re-render of the current route.
Route definition in App.jsx:
<Route path="/user-selector/:userId?" component={UserSelector} />
During that re-render, a componentDidUpdate lifecycle hook gets invoked. In there I am comparing the previous and current values of the URL parameter via the React Router's props.match.params object. If a change is detected, a Redux action gets fired to fetch new data.
Modified UserSelector.jsx:
componentDidUpdate (prevProps) {
const { match: { params: { userId: prevUserId } } } = prevProps
const { match: { params: { userId } } } = this.props
if (prevUserId === userId) {
return
}
// Fire a Redux action (previously this sat in the onChange handler)
this.props.setUser(userId)
}
When the results are ready, all React components subscribed to Redux get re-rendered.
And this is my attempt to visualise how the code's been structured:
If anyone could verify if this pattern is acceptable, I would be really grateful.
For step 3, I suggest a different approach which should be more in line with react-router:
react-router renders a component based on a route
this component should act as the handler based on the particular route it matches (think of this as a container or page component)
when this component is mounted, you can use componentWillMount to fetch (or isomorphic-fetch) to load up the data for itself/children
this way, you do not need to use componentDidUpdate to check the URL/params
Don't forget to use componentWillUnmount to cancel the fetch request so that it doesn't cause an action to trigger in your redux state
Don't use the App level itself to do the data fetching, it needs to be done at the page/container level
From the updated code provided in the question:
I suggest moving the logic out, as you would most likely need the same logic for componentDidMount (such as the case when you first hit that route, componentDidUpdate will only trigger on subsequent changes, not the first render)
I think it's worth considering whether you need to store information about which user is selected in your Redux store and as part of URL - do you gain anything by structuring the application like this? If you do, is it worth the added complexity?

How can I pass the value from my function to another component in another file

I'm making a react application that shows details about the pokemon you searched. So I have a Home component which has an input field + submit button.
I want to render my api call in my Main component.
The question that I have is : How can I pass the value from this input field that is located in my home component to my main component when I enter submit?
home component details
I need this value to update my pokemon name state in my Main component in order to get the pokemon name for my fetch call.
I need this value to be stored inside my 'searchValue' state.
Main component details
any tips?
One of the ways you can achieve this by Using ref in reactjs , Inside your Main Component you need to make a reference, like :
componentDidMount() {
this.props.onRef(this);
}
// Delete the reference once component is unmounted
componentWillUnmount() {
this.props.onRef(undefined);
}
And then create a method , which will receive the values from a the Home component and then setState like :
method(values) {
this.setState({ searchValue: values });
}
Now inside your Home component you need to reference method component before your input like , (You can amend it accordignly)
import Home from './Home'
<Home onRef={ref => (this.home = ref)} />
<Form onSubmit={e => { this.onSubmit(e) }}
Make sure to add onSubmit method inside main component which will send the values to Home Component
onSubmit = values => {
this.home.method(values);
}
You can read more about Ref and the DOM on React Documentation
I would propose you to take a look at redux which is great tool for state management. Redux will ensure you have a single source of truth.
What you will have to do is create an Action which would dispatch an event to update the state, a Reducer which would update the state (value of the search-key in redux-store). Your Main component will read the data from the API (Same Action-Reducer way) & render it without having to bother about the current state. I can explain the whole architecture in detail if you are more interested. This architecture will have less bugs & clear flow of information including shared state between components.
I know this sounds bit too complex but its worth a try if your application is growing.
Few suggestions:
Your Main component has too much of logic. Components should have least logic as possible so they are more deterministic. Please also read about React Stateless functional components
Here are some links if you wish to take redux path
10-tips-for-better-redux-architecture
Redux Best Practices

Categories