React page doesn't load the new content - javascript

I'm trying to redirect page declaratively.
Here is my Header.js:
const Header = () => {
const [data, setData] = useState({ objects: [] });
useEffect(async () => {
const result = await axios.get(
"http://example.com/api/v1/categories"
);
setData(result.data);
}, []);
return (
<div>
{courses.map( objects => (
<Link to={`/cats/${objects.id}`}>{objects.title}</Link>
))}
</div>
);
};
export default Header;
Here is my App.js
class App extends Component {
render(){
return (
<Router>
<Redirect exact from="/" to="/index" />
<Route path = "/" component = {App}>
<Header/>
<Route path="/cats/:objectId" component={CoursePage} />
</Route>
</Router>
);
}
}
export default App;
Here is the CoursePage.js:
const CoursesPage = (props) => {
let { objectId } = useParams();
const [data, setData] = useState({ courses: [] });
useEffect(() => {
(async () => {
const result = await axios.get(
"http://example.com/api/v1/cats/"+objectId
).catch(err => {
console.error(err);
});
setData(result.data);
})();
}, []);
return (
<Fragment>
{courses.title}
</Fragment>
);
};
export default CoursesPage;
On Header I click to links. It redirects to course page successfully. But when I click again another course link, it doesn't reload the component and it doesn't load the new data. The URL changes, but page is not.
How can I force the page to reload the component?

You should change the dependency array of useEffect in CoursePage to depend on objectId, whenever object id will change, it will rerun the effect.
const CoursesPage = (props) => {
let { objectId } = useParams();
const [data, setData] = useState({ courses: [] });
useEffect(() => {
(async () => {
setData({ courses: [] }); // add this line
const result = await axios.get(
"http://example.com/api/v1/cats/"+objectId
).catch(err => {
console.error(err);
});
setData(result.data);
})();
}, [objectId]); // Update dependency array here
return (
<Fragment>
{courses.title}
</Fragment>
);
}
export default CoursesPage;
This will reset the previous data before to load new one as soon as object id changes.
If you want to remount component completely on param change in url, then you can add an id to your component root element.
return (
<Fragment key={objectId}>
{courses.title}
</Fragment>
);
More Info here...

Related

Why props.children are not rendered on the first render? I have to refresh the page, so then props.children appear on the page

I'm working on the web-app that has a role choice when you visit for the first time. After I choose the admin role for example, it sets the value of userRole in localStorage.
Here is App you can see that depending on the current role there are different routes.
After I have the role chosen, I'm redirected to the '/' route and I actually want to see the component that renders this route which is TableBoard in this case, but instead I firstly get render={() => <Container>its from app.tsx {route.component}</Container>}. So on the first render I only see its from app.tsx, then I refresh the page and everything is fine.
How to fix routing here?
Please ask if something is unclear.
function App() {
const currentRole = useReduxSelector(state => state.auth.currentRole);
const accessToken = localStorage.getItem('accessToken');
const role = localStorage.getItem('role');
const getCurrentRoutes = () => {
if (role === 'userCollaborator') {
return AccRoutes;
} else if (role === 'userAdmin') {
return AdminRoutes;
} else if (role === 'userHr') {
return HRRoutes;
} else if (role === 'userPartner') {
return Routes;
} else {
return RoutesAuth;
}
};
const routes = getCurrentRoutes();
let routesComponents;
if (currentRole && accessToken) {
routesComponents = routes?.map(route => {
return (
<Route
key={route.name}
exact
path={route.path}
render={() => <Container>its from app.tsx {route.component}</Container>}
/>
);
});
} else {
routesComponents = routes?.map(route => {
return (
<Route
key={route.name}
exact
path={route.path}
component={route.component}
/>
);
});
}
return (
<BrowserRouter>
<Provider store={store}>{routesComponents}</Provider>
</BrowserRouter>
);
}
export default App;
Component that should be rendered for the route I'm redirected:
export const TableBoard = () => {
return (
<Container>
<div>here I have a lot of other content so it should be rendered as props.children in Container</div>
</Container>
);
};
And the code for Components.tsx looks this way:
const Container: React.FC<ContainerProps> = ({children}) => {
return (
<div>
{children}
</div>
);
};
export default Container;
Because localStorage.getItem('role') won't make react rerender your component. You can make a provider to handle this situation.
For example:
// RoleProvider.jsx
const RoleContext = React.createContext();
export const useRoleContext = () => {
return React.useContext(RoleContext)
}
export default function RoleProvider({ children }) {
const [role, setRole] = React.useState();
React.useEffect(
() => {
setRole(localStorage.getItem('role'));
},
[setRole]
)
const value = React.useMemo(
() => ({
role,
setRole: (role) => {
localStorage.setItem('role', role);
setRole(role);
}
}),
[role, setRole]
);
return (
<RoleContext.Provider value={value}>
{children}
</RoleContext>
)
};
Then, you can put the RoleProvider above those children who need the role value. And use const { role } = useRoleContext() in those children. Since role is stored in a state. Whenever you update the role by the following code, those children who use const { role } = useRoleContext() will be rerendered.
const { setRole } = useRoleContext();
// Please put it in a useEffect or handler function
// Or this will cause "Too many rerenders"
setRole('Some Role You Want')

React Component wont render with given state from the parent component

I need to render a component that has a route using react router. the first component has a button that when clicked needs to render another component that has state passed in from the first component. All objects and strings from the first component show in the console.log of the child component but it wont set state when I use setProfile(p).
const Member = (props)=> {
const [user, setUser] = useState({});
const [profile, setProfile] = useState({});
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
const a = await snInstance.getUsersPosts(pro, { from: accounts[0] });
console.log(a)
console.log(p)
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile, state]);
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div class="container">
<a target="_blank">Name : {profile.name}</a>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
{p.message}
</tr>})}
</div>
)
}
export default Member;
This is the parent component I want to redirect from
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
I have this component working when I don't have a dynamic route that needs data passing in from the parent component It's redirecting from.
This is my routes.js file
const Routes = () => {
return (
<Switch>
<Route path="/posts" exact component={Posts} />
<Route path="/member" exact component={Member} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
https://codesandbox.io/s/loving-pine-tuxxb

What is the best way to pass in props to a react router route?

I have a react component I need to render that takes one argument of a string when it is initialized. I need a button to click on that will redirect and render this new component with the string. It sends the string I want when it console.log(pro). everytinme I click on the button it goes to a blank screen and doesn't load.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={() => <Member user={props.state.member} />} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
The original component looks like this
const Posts = (props) => {
const dispatch = useDispatch();
const [postCount, setPostCount] = useState(0);
const [member, setMember] = useState({});
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member'
);
console.log('----------- member------------') // console.log(addr)
return (
<Member user={member}><Member/>
);
}
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
The component I'm trying to render from the Posts component needs to be rendered with a string
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = props.location;
const [profile, setProfile] = useState({});
useEffect(() => {
const doEffects = async () => {
try {
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile]);
return (
<div class="container">
{profile.name}
</div>
)
}
export default Member;
You can pass an extra data to a route using state attribute with history.push
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push({
path: '/member',
state: { member }
});
}
Once you do that you can access it in the rendered route from location.state
import {
useLocation
} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = useLocation();
console.log(state.member);
const [profile, setProfile] = useState({});
...
}
export default Member;
Also you do not need to pass on anything while rendering the Route
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes

How to use React component's custom hook with "map"

I'm trying to make a Checkbox component.
Here is my Checkbox.tsx.
import React from "react";
import * as S from "./style";
const Checkbox: React.FC<S.ICheckboxProps> = ({ checked, setChecked }) => {
return <S.StyledCheckbox checked={checked} onClick={setChecked} />;
};
and this is my useCheckbox.tsx,
import { useState } from "react";
export const useCheckbox = (initialState: boolean) => {
const [checked, _setChecked] = useState<boolean>(initialState);
const setCheckedToggle = () => _setChecked((prev) => !prev);
const setCheckedTrue = () => _setChecked(true);
const setCheckedFalse = () => _setChecked(false);
return { checked, setCheckedToggle, setCheckedTrue, setCheckedFalse };
};
export default Checkbox;
It works good. I can use this like
import Layout from "components/Layout";
import { useCheckbox } from "hooks/useCheckbox";
import Checkbox from "components/Checkbox";
const Home = () => {
const { checked, setCheckedToggle } = useCheckbox(false);
return (
<Layout>
<Checkbox checked={checked} setChecked={setCheckedToggle} />
</Layout>
);
};
export default Home;
But I have trouble in the List component.
List has a Checkbox component, and I have to use this List with data.
const Home = ({data}) => {
return (
<Layout>
{data.map((d) => <List />)}
</Layout>
);
};
In this case, is there a way to determine if the list is selected?
If the List has useCheckbox, the Home component doesn't know the checked state.
Should I use useCheckbox in the Home component for data.length times? I think this is not good.
Thanks for reading, and Happy new year.
If you want the checkbox state to exist at the level of Home then you'll need state in the Home component that can handle multiple items, either as an array or object.
Then where you map over data you can pass down checked and setChecked as props to List, with all the logic defined in Home using the item index (or preferably an ID if you have one) in relation to your Home state.
Here's an example of a hook you could use in Home
import { useState } from "react";
export const useCheckboxes = () => {
const [checkedIds, setCheckedIds] = useState([]);
const addToChecked = (id) => setCheckedIds((prev) => [...prev, id]);
const removeFromChecked = (id) =>
setCheckedIds((prev) => prev.filter((existingId) => existingId !== id));
const isChecked = (id) => !!checkedIds.find(id);
const toggleChecked = (id) =>
isChecked(id) ? removeFromChecked(id) : addToChecked(id);
return { isChecked, toggleChecked };
};
And you would use it like this
const Home = ({ data }) => {
const { isChecked, toggleChecked } = useCheckboxes();
return (
<Layout>
{data.map((d) => (
<List
key={d.id}
checked={isChecked(d.id)}
toggleChecked={() => toggleChecked(d.id)}
/>
))}
</Layout>
);
};

What is the best way to fetch data in react?

I currently use contentful for data management, so it's not the usual axios/fetch method this time.
I'm using useContext to share the data to my components, and have an useEffect that sets the state with the new data. What is the problem you ask? The ugly syntax. When i pass the data in the second rerender i can't access the data immediately, the data[0][0] doesn't exist yet, so it throws an error. This results in this disgusting syntax: <h5>{data ? data[0].sys.contentType.sys.id : ""}</h5> It might be "fine", and it "works". But it looks absolutely atrocious to me.
App.jsx
const App = () => {
const contentfulHook = useState(null);
useEffect((e) => {
client.getEntries().then((res) => contentfulHook[1](res.items));
/*
OR - same result
(async () => {
const data = await client.getEntries().then((res) => res.items);
contentfulHook[1](await data);
})();
*/
//Remove preloader when content is loaded
setTimeout(() => {
const preloader = document.getElementById("preloader");
preloader.style.opacity = 0;
preloader.addEventListener("transitionend", (e) => {
preloader.remove();
});
}, 0);
}, []);
console.log(contentfulHook[0]);
return (
<contentfulContext.Provider value={contentfulHook}>
<BrowserRouter>
<Global />
<Pages />
</BrowserRouter>
</contentfulContext.Provider>
);
};
render(<App />, document.getElementById("root"));
I'd suggest two things on that matter:
Encapsulate that request logic in a custom hook, so you can remove that code from your component.
Use data, loading and error pattern (as used by Apollo and probably other libraries).
The result would be something like:
const App = () => {
const { data, loading, error } = useRequest();
return (
<contentfulContext.Provider value={data}>
<BrowserRouter>
<Global />
<Pages />
</BrowserRouter>
</contentfulContext.Provider>
);
};
const useRequest = () => {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
client.getEntries()
.then((res) => setData(res.items))
.catch((e) => setError(e))
.finally(() => {
removePreloader();
setLoading(false);
});
}, []);
return { data, loading, error };
}
const removePreloader = () => {
setTimeout(() => {
const preloader = document.getElementById("preloader");
preloader.style.opacity = 0;
preloader.addEventListener("transitionend", (e) => {
preloader.remove();
});
}, 0);
}
render(<App />, document.getElementById("root"));
Either way, you still need to check for the data before accessing the data attributes when rendering. Even if it is on a top-level condition like:
const App = () => {
const { data, loading, error } = useRequest();
return (
<contentfulContext.Provider value={data}>
<BrowserRouter>
<Global />
<Pages />
{data && <h5>{data[0].sys.contentType}</h5>}
</BrowserRouter>
</contentfulContext.Provider>
);
};

Categories

Resources