ReactJs, update state after redirect - reactjs

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);
})

Related

how to delay the rendering of render function until usereffect calls in react

i want to run the useEffect first before the render function which is placed inside the <Route /> tag starts to render. i expect to get currently available user details through the API and assigne them to render function.
but render function runs before the UseEffect retrieve data from the API. so help me to find the solution.
import React, { useEffect, useState } from "react";
import { Route, Redirect } from "react-router-dom";
import { Auth } from "aws-amplify";
const ProtectedRoute = ({ children, ...rest }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
setIsAuthenticated(
Auth.currentAuthenticatedUser({
// bypassCache: false,
})
.then((user) => console.log(user))
.catch((err) => console.log(err))
);
}, []);
return (
<Route
{...rest}
render={({ location }) =>
(isAuthenticated ) ? (
children
) : (
<Redirect
to={{
// pathname: "/login",
pathname: "/create-profile",
state: { from: location },
}}
/>
)
}
/>
);
};
export default ProtectedRoute;
Try this
useEffect(() => {
Auth.currentAuthenticatedUser({
// bypassCache: false,
})
.then((user) => user && setIsAuthenticated(true))
.catch((err) => err && setIsAuthenticated(false));
}, []);
You could wrap that authentication stuff into a hook of your own, and then simply not render anything until it's ready:
function useIsAuthenticated() {
const [isAuthenticated, setIsAuthenticated] = useState(null);
useEffect(() => {
Auth.currentAuthenticatedUser({})
.then(setIsAuthenticated)
.catch((err) => {
console.log(err);
setIsAuthenticated(false);
});
}, []);
return isAuthenticated;
}
const ProtectedRoute = ({ children, ...rest }) => {
const isAuthenticated = useIsAuthenticated(); // Will be the user if authenticated, null if busy, or false if error.
if (isAuthenticated === null) {
return null; // Don't render anything if authentication state is unknown
}
return <>...</>;
};

React variable value not replaced in api call

I am trying to use UseParam to get the id, i am trying to place it inside of my API request however when i console.log it the actual value doesn't go inside rather the text itself.
vesselComponents.js :
function VesselComponents() {
const { id } = useParams();
const api = async () => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${id}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
console.log(api);
const { components, error, loading } = useSelector(
(state) => state.components
);
const dispatch = useDispatch();
useEffect(() => {
fetchComponents()(dispatch);
}, [dispatch]);
const getTreeItemsFromData = (treeItems) => {
return treeItems.map((treeItemData) => {
let children = undefined;
if (treeItemData.children && treeItemData.children.length > 0) {
children = getTreeItemsFromData(treeItemData.children);
}
return (
<TreeItem
component={Link}
to={`./info/${treeItemData.id}`}
key={treeItemData.id}
nodeId={String(treeItemData.id)}
label={treeItemData.name}
children={children}
/>
);
});
};
const DataTreeView = ({ treeItems }) => {
return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
>
{getTreeItemsFromData(treeItems)}
</TreeView>
);
};
return (
<div className="components-container">
<div className="components-items">
<DataTreeView treeItems={components} />
</div>
<div className="component-detail">
<Outlet />
</div>
</div>
);
}
export default VesselComponents;
This is how the console.log look like :
async () => {
try {
const res = await axios__WEBPACK_IMPORTED_MODULE_3___default().get( // here
`http://127.0.0.1:8000/api/maintenance/${id}`);
return res.data;
} catch (err…
Also if i wanted to make this call rather in my slice how would i go about exporting this specific ID that changes so i can use it there.
This is because you actually log the function, not the return value.
I suppose you want to fetch the maintenance id as the component mounts. I advice you to use useEffect for this case.
import { useEffect, useState } from 'react'; // above the component's class declaration
// and inside your component
const [api, setApi] = useState(null); // null by default
useEffect(() => {
const fetchMaintenance = async () => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${id}`
);
return res.data;
} catch (error) {
throw Error(error);
}
});
};
fetchMaintenance()
.then((api) => {
setApi(api);
})
.catch((error) => {
console.log(error);
});
}, []);
And by that you can use the value of api anywhere you like.
For example to log it
useEffect(() => {
console.log(api);
}, [api]);
or to render it on your view
return (
return (
<div className="components-container">
{JSON.stringify(api)}
<div className="components-items">
<DataTreeView treeItems={components} />
</div>
<div className="component-detail">
<Outlet />
</div>
</div>
);
}

Prevent React from re-fetching data in parent component

In my parent component, Dashboard.tsx, I have a child component, Expenses.tsx, that makes an API fetch call and then displays the data. The parent component has a Router that allows you to navigate to different URL's in the parent component, which forces everything to re-render every time you navigate to a new path or render a new child component. How can I make it so that this fetch call is only made one time? I've tried using the useRef() hook but it re-initializes every time there is a re-render and I have the same problem.
Here is Dashboard.tsx:
export const Dashboard = () => {
const d = new Date()
const history = useHistory()
const { user, setUser } = useAuth()
const [categories, setCategories] = useState({
expenseCategories: [],
incomeCategories: []
})
const getCategories = async(user_id: number) => {
await fetch(`/api/getCategories?user_id=${user_id}`)
.then(result => result.json())
.then(result => setCategories(result))
}
useEffect(() => {
if (user.info.user_id) {
getCategories(user.info.user_id)
}
}, [])
const dashboardItems = [
{
value: 'Add Expense',
path: '/dashboard/addExpense'
},
{
value: 'Add Income',
path: '/dashboard/addIncome'
},
{
value: 'Logout',
path: '/login',
onClick : async() => {
localStorage.clear()
setUser({
info: {
user_id: null,
email: null,
username: null
},
token: null
})
},
float: 'ml-auto'
}
]
return(
<div>
<DashboardNavbar items={dashboardItems}/>
<div className="wrapper">
<p>{`Hello, ${user.info.username}!`}</p>
<DateAndTime />
<Expenses date={d}/>
<Income date={d}/>
<Switch>
<Route path='/dashboard/addExpense'>
<AddItemForm user={user} type={'expenses'} categories={categories.expenseCategories} />
</Route>
<Route path='/dashboard/addIncome'>
<AddItemForm user={user} type={'income'} categories={categories.incomeCategories} />
</Route>
</Switch>
<Logout />
</div>
</div>
)
}
And here is Expenses.tsx, where the fetch call is being made:
export const Expenses = (props: ExpensesProps) => {
const [isLoading, setIsLoading] = useState(true)
const { date } = props
const { user } = useAuth()
const m = date.getMonth() + 1
const s = '0'.concat(m.toString())
const [total, setTotal] = useState<number>(0)
useEffect(() => {
const getTotalExpenses = async() => {
await fetch(`/api/expenses?user_id=${user.info.user_id}&month=${s}`)
.then(response => response.json())
.then(result => {
if (result) {
setTotal(parseFloat(result))
}
})
.then(result => {
setIsLoading(false)
})
}
if (user.info.user_id) {
getTotalExpenses()
}
}, [])
return isLoading ? (
<div>
loading...
</div>
) : (
<div>
{`Your monthly expenses so far are: $${total}.`}
</div>
)
}

My Login component flashes whenever I reload my dashboard

I'm using reactjs to build a login/register system with authentication and authorization. if authenticated(jsonwebtoken), it should route me to the dashboard else redirect me back to login.
but whenever I reload it hits the login endpoint for a second then back to dashboard. how can I fix this?
Below is a giphy to show what I'm talking about
Here are the components associated with the issue stated above
App.js
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false)
// set isAuthenticated to true or false
const setAuth = (boolean) => {
setIsAuthenticated(boolean)
}
useEffect(() => {
// check if the person is still Authenticated
const isAuth = async () => {
try {
const res = await fetch('/auth/verify', {
method: 'GET',
headers: { token: localStorage.token},
})
const data = await res.json()
// if authenticated, then
if(data === true) {
await setIsAuthenticated(true)
} else {
await setIsAuthenticated(false)
}
} catch (err) {
console.error(err.message)
}
}
isAuth()
})
return (
<Fragment>
<Router>
<div className='container'>
<Switch>
<Route exact path='/login' render={props => !isAuthenticated ? <Login {...props} setAuth={setAuth} /> : <Redirect to='/dashboard' /> } />
<Route exact path='/register' render={props => !isAuthenticated ? <Register {...props} setAuth={setAuth} /> : <Redirect to='/login' />} />
<Route exact path='/dashboard' render={props => isAuthenticated ? <Dashboard {...props} setAuth={setAuth} /> : <Redirect to='/login' /> } />
</Switch>
</div>
</Router>
</Fragment>
);
Login Component
const Login = ({ setAuth }) => {
const [text, setText] = useState({
email: '',
password: ''
})
const { email, password } = text
const onChange = e => setText({ ...text, [e.target.name]: e.target.value})
const onSubmit = async (e) => {
e.preventDefault()
try {
// Get the body data
const body = { email, password }
const res = await fetch('/auth/login', {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: JSON.stringify(body)
})
const data = await res.json()
if(data.token) {
// save token to local storage
localStorage.setItem("token", data.token)
setAuth(true)
toast.success('Login Successful')
} else {
setAuth(false)
toast.error(data)
}
} catch (err) {
console.error(err.message)
}
}
return (
<Fragment>
<h1 className='text-center my-5'>Login</h1>
<form onSubmit={onSubmit}>
Dashboard Component
const Dashboard = ({ setAuth }) => {
const [name, setName] = useState('')
useEffect(() => {
const getName = async () => {
try {
const res = await fetch('/dashboard', {
method: 'GET',
// Get the token in localStorage into the header
headers: { token: localStorage.token }
})
const data = await res.json()
setName(data.user_name)
} catch (err) {
console.error(err.message)
}
}
getName()
// eslint-disable-next-line
}, [])
// Log out
const logOut = (e) => {
e.preventDefault()
localStorage.removeItem("token")
setAuth(false)
toast.success('Logged Out')
}
return (
<Fragment>
<h1 className='mt-5'>Dashboard</h1>
<p>Hello, {name}</p>
<button className='btn btn-primary my-3' onClick={e => logOut(e)}>Log Out</button>
</Fragment>
There are two problems that I found in your code above.
The first is that your ueEffect does not specify any dependency.
When the dependencies are not specified in this way the useEffect would run anytime any state changes.
useEffect(()=> {
// code here
}); // this one would run anytime any state changes in the component. You usually don't want this.
When a dependency array is specified, the code in the useEffect would run anytime any of the state in the dependencies changes.
useEffect(()=> {
// code here
},
[state1, state2, ...others...] //code would run when any of the state in this array changes
In your case, however, you probably want to run that useEffect once. To do this we add an empty array as the dependency value.
useEffect(()=> {
// code here
},
[] //empty deps means that the code runs only once. When the component mounts
)
Extra ideas
I also suggest that you add a loading state to your component so that you can show a loader while the API call is being made.
You might want to show a loader while the API call is being made(or even set this state to true by default since the API call is the first thing you do in your app)
.
Also, consider putting useEffect in a custom Hook

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

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

Resources