React-router re-render on URL change with route using regex - reactjs

I have an App like this:
function App() {
return (
<Router>
<OpNavbar/>
<Route exact={true} path="/" render={() => (
<h1>This is the welcome page!</h1>
)}/>
<Route path="/([a-z]{3,4})/([a-z]+)list" component={OpTable}/>
</Router>
);
}
If I am in "/" and switch paths by clicking a link to for example "/pfm/examplelist" and viceversa it renders the respective component without any problem. However if I am in say "/pfm/examplelist" and switch to "/pfm/anotherlist" the url changes but my component will not be re-rendered. I assume it's because both the old and the new paths match my regex? How can re-render my component on every url change?
Here is a stripped-down version of my Table component:
function OpTable(props) {
const [apiData, setData] = useState([]);
const [columns, setColumns] = useState([{dataField: "Dummy", text: "Loading, Please Wait..."}]);
useEffect(() => {
axios.get(props.match.url)
.then(response => {
let res_data = response.data;
setData(res_data.data);
setColumns(res_data.columns);
})
.catch(error => {
console.log(error);
})
}, [])
return (
<BootstrapTable
keyField="id"
data={ apiData }
columns={ columns }
/>
);
}

This is the case when subsequent url calls same component.If you want to rerender,one of the way is to track your path(url) in useEffect.
useEffect(() => {
axios.get(props.match.url)
.then(response => {
let res_data = response.data;
setData(res_data.data);
setColumns(res_data.columns);
})
.catch(error => {
console.log(error);
})
}, [props.location.pathname])
I am not a regular user of hooks(I may be wrong with the syntax). But the logic is to call api(whenever there is change in url) required for that component which in turn sets state and rerender happen

Related

How do I pass useState data from one component file to another ReactJS

I need to share some data I have collected with useState from one component file to another. I am building a blog using Material UI that displays all posts on the home page. When a specific post is clicked I want to redirect the user to a single post page where they can read the entire thing. I have searched StackOverflow and the internet and can't seem to find a straight forward answer to the problem. I think context API might be overkill. And I am unsure how to pass props from one component to another when I am not calling the function in the other component file.
This is part of the home page component. This code stores the post id that I need to access from the SinglePost component. When I click a post it correctly console.logs the id:
export default function HomeCard() {
const [latestPosts, setLatestPost] = useState([]);
useEffect(() => {
fetch('http://localhost:8000/posts')
.then ((res) => {
return res.json();
})
.then ((data) => {
setLatestPost(data.reverse());
});
}, []);
const navigate = useNavigate();
const storePostId = (singlePostId) => {
navigate('/single-post')
console.log(singlePostId);
}
A little further down in the file:
<CardActionArea
onClick={ () =>
storePostId(latestPost.id)
}>
Within my SinglePost component:
const SinglePost = () => {
useEffect(() => {
fetch(`http://localhost:8000/posts/${singlePostId}`)
.then ((res) => {
return res.json();
})
.then ((data) => {
return (data);
});
}, []);
You can pass the prob as a query parameter.
const storePostId = (singlePostId) => {
navigate(`/single-post/${singlePostId}`)
}
and in SinglePost component read the query params using your project's router (ie: react router dom)
Edit:
You're using useNavigate from react router. I'm assuming you've had the routes set up
<Routes>
<Route path="/single-post" element={<SinglePost />} />
</Routes>
I'd recommend setting it like this
<Routes>
<Route path="/single-post/:singlePostId" element={<SinglePost />} />
</Routes>
And in your SinglePost
const SinglePost = () => {
let { singlePostId } = useParams(); //It will read it from query params
useEffect(() => {
fetch(`http://localhost:8000/posts/${singlePostId}`)
.then ((res) => {
return res.json();
})
.then ((data) => {
return (data);
});
}, [singlePostId]);

ReactJs, update state after redirect

I have a problem with updating the state of a component after a redirect. Actually I have two components getItems.js and addItem.js
App.js
const [item, setItem] = useState([]);
useEffect(() => {
const fetch = () => {
axios.get('localhost:3000/api/get_all.php')
.then(response => {
setItems(response.data);
}).catch(error => {
console.log(error)
})
}
fetch();
}, [])
return (
<BrowserRouter>
<div className="container">
<Routes
<Route path="/" element={<getItems items={items}/>} />
<Route path='/add-item' element={<addItem />} />
</Routes>
</div>
</BrowserRouter>
)
getItems.js
This component is used to display each item from App.js
addItem.js
const onSubmit = (event) => {
const item = {
...
}
axios.post('localhost:3000/api/add_item.php', item)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
})
}
On App.js I have a button to direct to add-item page, once I save the item and redirect to App.js page the items state is not updated, only if I reload the entire page.
You need to set the state after adding an item. Since your logic is separated in two routes, you will have to use a contextProvider
Basically, it allows you to share state anywhere in your app.
ContextProvider.js
//Set initial value. We wont use it but we need t odefined it here
export const ContextProvider = createContext({
items: undefined
setItems: () => {}
});
App.js
export const App = () => {
const [items, setItems] = useState([])
//Pass state to context
const contextValue = {
items: items,
setItems: setItems,
};
useEffect(() => {
const fetch = () => {
axios.get('localhost:3000/api/get_all.php')
.then(response => {
setItems(response.data);
}).catch(error => {
console.log(error)
})
}
fetch();
}, [])
return (
<ContextProvider.Provider value={contextValue}>
<BrowserRouter>
<div className="container">
<Routes
<Route path="/" element={<getItems/>} />
<Route path='/add-item' element={<addItem />} />
</Routes>
</div>
</BrowserRouter>
</ContextProvider.Provider>
)
};
GetItems.js
//instead of prop.items
const {items} = useContext(Contextprovider)
addItem.js
const {items, setitems} = useContext(Contextprovider)
axios.post('localhost:3000/api/add_item.php', item)
.then(response => {
console.log(response.data);
//Validate if item is added
if(response.data) {
//Push new item to list
setItem([...item, response.data])
}
})
.catch(error => {
console.log(error);
})

why is data initially re-renders with empty and then displaying my productdetails page with delay while routing? used useCallback & useMemo

This is my code. By using useCallback and useMemo the initial empty re render is not controlled. Initially empty data gets rendered ,after routing the same product get replaced with the selected product.
Routing component
<Header/>
<Routes>
<Route path="/" element={<ProductListing/>} />
<Route exact path="/product/:productId" element={<ProductDetail/>} />
<Route>404 Not Found!</Route>
</Routes>
<Link to={`/product/${id}`}>`
...
const product = useSelector((state) => state.product);
const { productId } = useParams();
console.log(productId);
const { title, image, price, category ,description} = product;
const dispatch = useDispatch();
console.log(product);
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProducts(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
return () => {
dispatch(removeSelectedProducts());
};
}, [productId]);
please resolve the issue

How can I dynamically generate a PrivateRoute redirect based on an axios response?

I have an endpoint in my app called /get-redirect, which basically redirects you to wherever you need to be.
E.g. if you're not logged in, the response from this endpoint is an HTTP 200 with the redirect_location field in the response set to mysite.com/login. If you're logged in, but haven't completed step 2 of onboarding, it sends an HTTP 200 with redirect_location set to mysite.com/step2, etc.
I want to use PrivateRoute in React Router for authenticated pages, and I want any redirects to go to the result of the /get-redirect endpoint. I would then render the appropriate component in the statement.
This is what I have so far but I keep getting that the getPage() function is returning undefined. What am I missing here?
const fakeAuth = {
getPage(cb) {
if (document.cookie === null) {
return '/login'
}
const url = "https://api.mysite.com/get-redirect"
axios.get(url)
.then(function (response) {
return response.redirect_location
}).catch(function (error) {
if (error.response.status === 401) {
return '/401'
}
return '/404'
})
},
}
function PrivateRoute({ children, ...rest }) {
return (
<Route {...rest} render={() => {
return <Redirect to={{
pathname: fakeAuth.getPage()
}} />
}} />
)
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Router>
<Switch>
...
<PrivateRoute path="/dashboard">
<Dashboard />
</PrivateRoute>
....
Issue
getPage needs to return the Promise chain started by axios.
You will need to wait for the Promise to resolve (or reject) in the PrivateRoute component but you can't just return the result as the redirect target.
Solution
I suggest abstracting a component to do the endpoint check and conditionally render the redirect or the original children prop. While the endpoint is checked conditionally render some loading state or null until the expected result is returned then conditionally render the redirect or child Dashboard component.
Example
const CheckRedirect = ({ children }) => {
const [isChecking, setIsChecking] = React.useState(true);
const [target, setTarget] = React.useState("/");
React.useEffect(() => {
fakeAuth
.getPage()
.then((target) => {
setTarget(target);
})
.finally(() => setIsChecking(false));
}, []);
if (isChecking) return "...Checking"; // or loading spinner, or null
return target.redirect_location ? (
<Redirect to={target.redirect_location} />
) : (
children
);
};
function PrivateRoute({ children, ...rest }) {
return (
<Route {...rest} render={() => <CheckRedirect children={children} />} />
);
}
Demo
For demo this is the mocked auth getPage component, it has a 50% chance to return redirect target.
const fakeAuth = {
getPage(cb) {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(
Math.random() < 0.5 ? { redirect_location: "/step2" } : {}
);
}, 3000);
});
}
};

useEffect and SocketIO Infinite Rerendering, why is it happening?

I am getting data, using useEffect Hook from sockets. Every time I get the response, one particular component rerenders, but I don't pass any socket data to that component.
Code:
const App = () => {
const isMounted = React.useRef(false);
const [getSocketData, setSocketData] = useState([]);
useEffect(() => {
console.log('[App.js] Mounted!');
isMounted.current = true;
const socket = socketIOClient(process.env.REACT_APP_BOARD);
socket.on('ServersInfo', (data) => {
if (isMounted.current) {
setSocketData([...data]);
}
});
socket.on('connect_error', () => {
if (isMounted.current) {
setSocketData([]);
}
console.log('Connection error!');
});
return (() => {
isMounted.current = false;
socket.disconnect();
console.log('[App.js] unmounted');
});
}, []);
const routes = (
<Switch>
<Route path={['/', '/users/:id']} exact component={() => <MainPage axiosInstance={axiosInstance} />} />
<Route path='/servers' render={(spProps) => <ServerPing {...spProps} />} />
<Route render={(nfProps) => <NotFoundComponent {...nfProps} />} />
{/* <Redirect to='/' /> */}
</Switch>
);
return (
<div className="App">
<Layout>
<Suspense fallback={<p>Loading...</p>}>
{routes}
</Suspense>
</Layout>
</div>
);
};
export default App;
What my Components look like:
- App.js
- Layout (doesn't rerender) (which has 3 children) - MainPage (rerenders infinitely), ServerPing (doesn't rerender), NotFoundComponent (doesn't rerender)
The question is: why MainPage Component rerenders infinitely?
I mean MainPage Component and its children unmount and mount again, when socket data fetches which is weird behaviour.
MainPageComponent:
const MainPage = ({ axiosInstance, ...props }) => {
const isMounted = React.useRef(false);
const [loadingPage, setLoadingPage] = useState(true);
const [usernames, setUsernames] = useState([]);
const [currentDay] = useState(new Date().getDay());
useEffect(() => {
isMounted.current = true;
console.log('[MainPage.js] Mounted!');
getUsers();
return () => {
console.log('[MainPage.js] Unmounted!');
isMounted.current = false;
};
}, []);
const getUsers = async () => {
try {
const res = await axiosInstance.get('/users');
const newData = await res.data;
const newArray = [];
newData.map(user => (
newArray.push({id: user._id, flag: user.flag, text: user.name, value: user.name.toLowerCase()})
));
if (isMounted.current) {
setUsernames(newArray);
setLoadingPage(false);
}
} catch {
if (isMounted.current) {
setLoadingPage(false);
}
}
};
return...
Problem is that you are using component prop instead of render prop to render the MainPage which will remount the component on any re-render of App component if there is a callback given to a component prop.
Below code should fix the issue
<Route
path={['/', '/users/:id']}
exact
render={(props) => <MainPage {...props} axiosInstance={axiosInstance} />}
/>
According to the react-router-dom docs
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).

Resources