React app, API call that fetches routes for app - reactjs

Hello I am using react and redux, i have my action creator that fetches the routes of the page and i creat the routing with them this way:
First in app.js im calling the action creator (using mapDispatchToProps) in UseEffect and passing the result (mapStateToProps) to the Routes component:
useEffect(() => {
fetchMenu();
}, []);
<Routes menu={menu} />
Then in Routes.js:
{menu.map((item) => (
<PrivateRoute
key={item.id}
exact
path={item.link}
parentClass="theme-1"
component={(props) => selectPage(item.linkText, props)}
/>
))}
The problem is that if I refresh the page, there is a little delay between the api call and the render of the page, so for one second the browser shows "NOT FOUND PAGE" and then instantly redirect to the route. How can I make it work properly? Thank you !

Basically what you want is to be able to know that the data hasn't been loaded yet, and render differently based on that. A simple check would be see if the menu is empty. Something like this:
export const Menu = ({ menu, fetchMenu }) => {
useEffect(() => {
fetchMenu();
}, []);
if ( menu.length > 0 ) {
return <Routes menu={menu} />
} else {
return <MenuLoading />
}
}
A more advanced setup would be able to tell the difference between an empty menu due to an API error and a menu that's empty because it's still loading, but to do that you would need to store information about the status of the API call in the state.

Related

What am I doing wrong with React context?

Quick context, I am making a character-building app (think D&D Beyond). Users can log in, use the builder to generate a character, and then later recall and level-up that character. I'm hosting the data on MongoDB and using Realm Web for CRUD on the database. Based on this guide, I'm using React Context to keep track of the logged-in user as they navigate the site. I'm trying to create a widget in the NavMenu that will allow me to see the logged-in user for dev purposes (later, I can make a "Welcome, [name]!"-type greeting), but I keep getting the error that "mongoContext.user" is null. This makes sense, as the Context is set to null with useState, but as soon as the page renders there is an init() function which anonymously logs in the user if they aren't already. I can't seem to understand why this is happening, even after looking at 4 different articles on how to use React Context. Code Snippets below:
App.js
const [client, setClient] = useState(null);
const [user, setUser] = useState(null);
const [app, setApp] = useState(new Realm.App({id: process.env.REACT_APP_REALM_APP_ID}));
//Anonymously logs in to mongoDB App instance on page render
useEffect(() => {
const init = async () => {
if (!user) {
setUser(app.currentUser ? app.currentUser : await app.logIn(Realm.Credentials.anonymous()));
}
if (!client) {
setClient(app.currentUser.mongoClient('mongodb-atlas'));
}
}
init();
}, [app, client, user]);
//Function to render a given component and pass it the mongoContext prop
const renderComponent = (Component, additionalProps = {}) => {
return (
<MongoContext.Consumer>
{(mongoContext) => <Component mongoContext={mongoContext} {...additionalProps} />}
</MongoContext.Consumer>
);
}
return (
<MongoContext.Provider value={{app, client, user, setClient, setUser, setApp}}>
<Layout>
<Switch>
<Route exact path="/" render={() => renderComponent(Home)} />
... other Routes ...
</Switch>
</Layout>
</MongoContext.Provider>
);
}
As outlined in the guide, each Route receives a render function which wraps it in the Context.Consumer component and sends context. I couldn't figure out if this was possible with the Layout component (since it is itself a component wrapper), so instead I used React.useContext() inside the component I need context for: (the component relationship is Layout -> NavMenu -> UserDetail)
UserDetail.jsx
const UserDetail = (props) => {
const mongoContext = React.useContext(MongoContext);
return (
<div>
<h1>Logged in as: {mongoContext.user.id}</h1>
</div>
);
}
This is the component which gives the "mongoContext.user is null" error. The other components seem to use context just fine, as I have done some Create operations with the anonymous user. I can see that it probably has something to do with how the other components are rendered with the Consumer wrapper, but according to every guide I've read, the React.useContext in the UserDetail component should work as well. Thanks for any help, this has completely stymied the project for now.

Transferring data from one page to another

I have a page called Games in my React app and my React Routes are in the App.js file. On that Games page, if I click myButton, I want it to go to another page called Analyze Game and then populate some variables there. But when I normally open Analyze Game (without clicking myButton), it defines and populates a bunch of state and variables. The reason is so that you can analyze a game you manually enter, rather than picking from the list on the Games page. So I am a bit puzzled on how I can transfer the game data from the Games page to the Analyze Game page and then populate some variables there only if someone came from the games page.
I found this link that shows you can use history.push to get some data that was passed from one page to another: React-router - How to pass data between pages in React?


But how do you then only populate the variables on the Analyze Game page if you came from the games page?
Would you set a flag or something? What is best practice?
This is a good question, and normally can be done in two ways. I'll briefly mention them. Of course, there're other ways ;)
props
React likes props, so if you can solve it with that, that should be your first approach.
const App = () => {
const [value, setValue] = useState(...)
return (
<Router>
<Route1 render={() => <GamePage value={value} />} />
<Route2 render={() => <AnalyzePage value={value} />} />
</Router>
)
}
If you want to change this value inside either Game or Analyze, pass the setValue inside as well.
context
If you have very nested components inside the route, sometimes people like to use a context.
const App = () => {
const [value, setValue] = useState(...)
return (
<Context.Provider value={{ value, setValue}}>
<Router>
<Route1 render={() => <GamePage />} />
<Route2 render={() => <AnalyzePage />} />
</Router>
</Context.Provider>
)
}
const ComponentInsideGame = () => {
const { value } = useContext(Context)
...
}
global
If it turns out you want to send something behind React, for instance you don't want to trigger render at all for these shared data. You could do a global context with a ref.
const App = () => {
const ref = useRef({
gameConfiguration: ...,
runGame: () => {}
))
return (
<Router>
<Route1 render={() => <GamePage />} />
<Route2 render={() => <AnalyzePage />} />
</Router>
)
}
const ComponentInsideGame = () => {
const { current } = useContext(Context)
current.gameConfiguration = {...}
current.runGame()
...
}
route state
The link you provided is another way with browser state, however this actually isn't a react way, it's a browser way IMHO. But you know what, anyway works for you is fine :)

React - update or re-render parent component when navigating to another child

I have two components ChildA and ChildB which share a fair amount of UI elements plus the same logic to retrieve data from API before the component is rendered. The common logic and UI elements are externalized in Parent component.
The API call is executed from Parent in useEffect hook.
The common UI elements are rendered based on the data retrieved by the API call.
Problem:
When I navigate from /parent/a to /parent/b, the API call is not executed so the component doesn't update.
I guess the reason is Parent has already been mounted the first time I entered {base_url}/parent/a in the browser, so it doesn't execute useEffect a second time when I try to navigate to /parent/b. However I have no idea how to solve it. Am I going all wrong here?
Steps to reproduce:
Enter URL {base_url}/parent/a in the browser ; the API call is executed, everything works fine
Click a link to navigate to /parent/b ; the component doesn't update.
Requirements:
I would like to avoid using class components as much as possible
I don't use redux and I don't want to use it just to solve this problem
Routing:
<Route path="/parent/a" render={props => <Parent child={ChildA} name="child-a" {...props} /> } exact />
<Route path="/parent/b" render={props => <Parent child={ChildB} name="child-b" {...props} /> } exact />
Child A :
export default function ChildA() {
return (
<div>
<h1>Child A</h1>
{/*Clicking this link will reproduce the issue*/}
<Link to="b">Go to Child B</Link>
</div>
)
}
Child B :
export default function ChildB() {
return (
<div>
<h1>Child B</h1>
{/*Clicking this link will reproduce the issue*/}
<Link to="a">Go to Child A</Link>
</div>
)
}
Parent :
interface ParentProps {
child: any,
name: string
}
export default function Parent(props: ParentProps) {
const Child = props.child
const [model, setModel] = useState<Model | null>(null)
useEffect(() => {
fetchData()
}, [])
function fetchData() {
console.log('EXECUTING API CALL FOR:', props.name)
// ...API call ommitted for brevity...
}
return (
<div>
<h1>Parent</h1>
{/* ...Display lots of UI depending on Model - omitted for brevity... */}
<Child/>
</div>
)
}
Since you're passing an empty dependencies array, the effect only gets run once. You can add props.name to the dependencies array to make sure fetchData gets called when props.name changes.
useEffect(() => {
function fetchData() {
console.log('Executing API call for:', props.name)
// Fetch data
}
fetchData()
}, [props.name])
The fetchData function is added inside the effect to make the effect's dependencies clearly visible. I would recommend reading this for more information. And you can use the exhaustive-deps ESLint rule which is a part of the eslint-plugin-react-hooks package to help you find effects where the dependencies array does not include all the effect's dependencies.

My Login Form is showing even when I am already authenticated using React Routing?

I am using redux saga in my app, I have a login form and when I get authenticated I use the Redirect Component to move to the app, I do this of course after changing my connectedUser state, that is like that :
const initialState = {
loading: false,
user: {},
status: ""
};
When authenticating my state change to status:"AUTH" and user: { // user data }, And this is how I redirect to the Application component and this is how my Authentication component rendering method looks like :
render() {
if (this.props.connectedUser.status === "AUTH") {
return <Redirect to="/" />;
}
return MyLoginForm
}
My routes are defined in the whole app container :
function App(props) {
return (
<ThemeProvider theme={theme}>
<I18nProvider locale={props.language.language}>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Application}></Route>
<Route path="/authenticate" component={Authentification}></Route>
</Switch>
</BrowserRouter>
</I18nProvider>
</ThemeProvider>
);
}
const msp = state => ({ language: state.language });
export default connect(msp, null)(App);
This is working fine, the disconnect button is doing fine too, but the problem occurs when I am redirected to my app ( meaning this route "/" ).
I want that when I refresh the page, I don't get redirected to the authentication page, Why I am going there ? it is obvious that my state is the initial state again, the status is "" again.
I am not asking for code but an idea, how to implement this.
I was trying to put that status in my localStorage but I don't feel like it is a good practise, also I found a problem whith this because If I change the localStorage then I will have to re render the component so the condition can be verified.
I want that when I refresh the page, I don't get redirected to the authentification page, Why I am going there ? it is obvious that my state is the initial state again, the satus is "" again.
Sure! Refresh page will initial state again.
First of all, you need store current authentification after signed in. I suggest you use the redux-sessionstorage
Next, in Authentication component you should dispatch an action to get current authentification and update status in your state.

Component render() triggered after history.push but page is blank

I want the user to be redirected to the resources list after he deleted an item on its show page. I 've read a lot of SO Q&A on the topic, but I think I have a different issue as my routes and component got hit the right way after history.push
I tracked code execution through debugger till component render and still don't understand why nothing is returned
Here are my routes in my App component (wrapped this way<Router><App /></Router>) component :
<Route component={AppHeader} />
{["/articles/:id/edit", "/articles/new"].map((path, index) =>
<Route key={index} exact path={path} component{ArticleForm}/>
)}
<Route exact path="/articles/:id" component={Article}/>
{["/", "/articles"].map((path, index) =>
<Route key={index} exact path={path} component{ArticlesList}/>
)}
I use redux so Router and ArticleList are exported this way :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component))
In AppHeader component, a delete button is provided, if user is on show or edit page. When clicking on the link, following method is triggered :
class AppHeader extends Component {
...
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.props.history.push("/")
}
...
}
Then Route with ArticlesList is triggered and should render this component. Here is what happens (breakpoints all the way in render methods):
URL is updated
Router is rendered
App header is rendered
Article list is rendered
Article list re-rendered with fecth from API (state populated with list)
Article list re-rendered with componentDidUpdate
BUT page stays blank ... I am using this.props.history.push("/") in other components and it works fine (list get re-rendered and displayed). See blank page and console.logs :
If I manually reload the page, it renders normally.
What is preventing any component to be displayed (DOM is nearly empty, I only get my empty <div id="root"></div>) in this case ?
Do one thing ,to displaying Article list use filter to update list state
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})
}
Change :
this.props.history.push("/")
To :
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})

Resources